mirror of
https://github.com/chenasraf/vim-matchup.git
synced 2026-05-18 01:38:57 +00:00
Initial commit, very rough state
This commit is contained in:
57
CONTRIBUTING.md
Normal file
57
CONTRIBUTING.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Issue descriptions
|
||||
|
||||
Please see the [issue template](ISSUE_TEMPLATE.md) for how to write a good
|
||||
issue description. In short, it should contain the following:
|
||||
|
||||
1. Describe the issue in detail, include steps to reproduce the issue
|
||||
2. Include a minimal working example
|
||||
3. Include a minimal vimrc file
|
||||
|
||||
# Guide for code contributions
|
||||
|
||||
## Branch model
|
||||
|
||||
matchup is developed mainly through the master branch, and pull requests should
|
||||
be [fork based](https://help.github.com/articles/using-pull-requests/).
|
||||
|
||||
## Documentation style
|
||||
|
||||
Vim help files have their own specific syntax. There is a Vim help section on
|
||||
how to write them, see [`:h
|
||||
help-writing`](http://vimdoc.sourceforge.net/htmldoc/helphelp.html#help-writing).
|
||||
|
||||
The matchup documentation style should be relatively clear, and it should be
|
||||
easy to see from the existing documentation how to write it. Still, here are
|
||||
some pointers:
|
||||
|
||||
- Max 80 columns per line
|
||||
- Use the help tag system for pointers to other parts of the Vim documentation
|
||||
- Use line of `=`s to separate sections
|
||||
- Use line of `-`s to separate subsections
|
||||
- The section tags should be right aligned at the 79th column
|
||||
- Sections should be included and linked to from the table of contents
|
||||
|
||||
## Code style
|
||||
|
||||
When submitting code for matchup, please adhere to the following standards:
|
||||
|
||||
- Use `shiftwidth=2` - no tabs!
|
||||
- Write readable code
|
||||
- Break lines for readability
|
||||
- Line should not be longer than 80 columns
|
||||
- Use comments:
|
||||
- For complex code that is difficult to understand
|
||||
- Simple code does not need comments
|
||||
- Use (single) empty lines to separate logical blocks of code
|
||||
- Use good variable names
|
||||
- The name should indicate what the variable is/does
|
||||
- Variable names should be lower case
|
||||
- Local function variables should be preceded with `l:`
|
||||
- Prefer single quoted strings
|
||||
- See also the [Google vimscript style
|
||||
guide](https://google.github.io/styleguide/vimscriptguide.xml)
|
||||
- Use markers for folding
|
||||
- I generally only fold functions, and I tend to group similar functions so
|
||||
that when folded, I get a nice structural overview of a file
|
||||
- See some of the files for examples of how I do this
|
||||
|
||||
13
ISSUE_TEMPLATE.md
Normal file
13
ISSUE_TEMPLATE.md
Normal file
@@ -0,0 +1,13 @@
|
||||
### Explain the issue
|
||||
|
||||
Most issues are related to bugs or problems. In these cases, you should
|
||||
include a minimal working example and a minimal vimrc file (see below), as
|
||||
well as:
|
||||
|
||||
1. Steps to reproduce
|
||||
2. Expected behaviour
|
||||
3. Observed behaviour
|
||||
|
||||
If your issue is instead a feature request or anything else, please
|
||||
consider if minimal examples and vimrc files might still be relevant.
|
||||
|
||||
22
LICENSE.md
Normal file
22
LICENSE.md
Normal file
@@ -0,0 +1,22 @@
|
||||
MIT license
|
||||
|
||||
Copyright (c) 2017 Andy Massimino
|
||||
Copyright (c) 2016 Karl Yngve Lervåg
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
238
README.md
Normal file
238
README.md
Normal file
@@ -0,0 +1,238 @@
|
||||
# matchup.vim
|
||||
|
||||
<img src='https://github.com/andymass/matchup.vim/wiki/images/teaser.jpg' width='300px' alt='and in this corner...'>
|
||||
|
||||
match-up is a replacement for the venerable vim plugin matchit.vim.
|
||||
match-up aims to replicate all of matchit's features, fix a number of its
|
||||
deficiencies and bugs, and add a few totally new features.
|
||||
|
||||
## Overview
|
||||
|
||||
## Feature list
|
||||
|
||||
| feature | __matchup__ | matchit | matchparen |
|
||||
| -------------------------------- | ------------- | ------------- | ------------- |
|
||||
| jump between matching constructs | :thumbsup: | :thumbsup: | :x: |
|
||||
| jump to open, close | :thumbsup: | :question: | :x: |
|
||||
| jump inside | :thumbsup: | :question: | :x: |
|
||||
| full set of text objects | :thumbsup: | :x: | :x: |
|
||||
| auto-insert open, close, and mid | :thumbsup: | :x: | :x: |
|
||||
| auto-completion | :thumbsup: | :x: | :x: |
|
||||
| parallel transmutations | :thumbsup: | :x: | :x: |
|
||||
| highlight ()[]{} | :thumbsup: | :x: | :thumbsup: |
|
||||
| highlight _all_ matches | :thumbsup: | :x: | :x: |
|
||||
| modern, modular coding style | :thumbsup: | :x: | :x: |
|
||||
| actively developed | :thumbsup: | :x: | :x: |
|
||||
|
||||
Legend: :thumbsup: supported. :construction: TODO, planned, or in progress.
|
||||
:question: poorly implemented, broken, or uncertain. :x: not possible.
|
||||
|
||||
### Feature Documentation
|
||||
|
||||
What do we mean by open, close, mid? Here is an example:
|
||||
|
||||
```vim
|
||||
if l:x == 1
|
||||
call one()
|
||||
else
|
||||
call two()
|
||||
elseif
|
||||
call three()
|
||||
endif
|
||||
```
|
||||
|
||||
The words `if`, `else`, `elseif`, `endif` are called "constructs." The
|
||||
open construct is `if`, the close construct is `endif`, and the mid
|
||||
constructs are `else` and `elseif`. The `if`/`endif` pair is called an
|
||||
"open-to-close" block and the `if`/`else`, `else`/`elsif`, and
|
||||
`elseif`/`endif` are called "any" blocks.
|
||||
|
||||
- jump between matching constructs
|
||||
- `%` forwards matching construct `[count]` times
|
||||
- `{count}%` forwards `{count}` times. Requires
|
||||
`let g:matchup_override_Npercent = 1`
|
||||
- `g%` backwards matching construct `[count]` times
|
||||
|
||||
- jump to open and close
|
||||
- `[%` go to `[count]` previous unmatched open construct
|
||||
- `]%` go to `[count]` next unmatched close construct
|
||||
|
||||
- jump inside
|
||||
- `z%` to inside nearest `[count]`th inner contained block.
|
||||
|
||||
- full set of text objects
|
||||
`i%` the inside of an open to close block
|
||||
`1i%` the inside of an any block
|
||||
`{count}i%` If count is not 1, the inside open-to-close block
|
||||
|
||||
`a%` an open-to-close block.
|
||||
`1a%` an any block. Includes mids but does not include open and close.
|
||||
`{count}a%` if `{count}` is greater than 1, the `{count}` surrounding open-to-close block.
|
||||
|
||||
Note: by default objects involving `matchpairs` such as `(){}[]` are
|
||||
performed character-wise, while `matchwords` such as `if`/`endif` are
|
||||
performed line-wise.
|
||||
The -wise can be forced using "v", "V", or `^V`
|
||||
Let `g:matchup_all_charwise`.
|
||||
XXX inclusive, exclusive
|
||||
XXX need () characterwise, others linewise except QUIRKS.
|
||||
|
||||
- end-wise completion
|
||||
|
||||
Typing `CTRL-X <cr>` will insert the corresponding end construct.
|
||||
|
||||
- automatic block insertion
|
||||
|
||||
_Planned_. Typing `CTRL-X CTRL-B` to produce block skeletons.
|
||||
|
||||
- auto-completion
|
||||
|
||||
_Planned_. Typing `CTRL-X %` to give a menu of possible constructs.
|
||||
|
||||
- parallel transmutations
|
||||
|
||||
In insert mode, after changing a construct, typing `CTRL-G %` will
|
||||
change any matching constructs in parallel. As an example,
|
||||
|
||||
```latex
|
||||
\begin{equation}
|
||||
x = 10
|
||||
\end{equation}
|
||||
```
|
||||
|
||||
Appending a `*` and typing `CTRL-G %` will produce:
|
||||
|
||||
```latex
|
||||
\begin{equation*}
|
||||
x = 10
|
||||
\end{equation*}
|
||||
```
|
||||
|
||||
This must be done before leaving insert mode. A corresponding normal mode
|
||||
command is planned.
|
||||
|
||||
_Planned_: `g:matchup_auto_transmute`
|
||||
|
||||
### Options
|
||||
|
||||
## Installation
|
||||
|
||||
If you use vim-plug, then add the following line to your vimrc file:
|
||||
|
||||
Plug 'andymass/matchup'
|
||||
|
||||
Or use some other plugin manager:
|
||||
|
||||
- vundle
|
||||
- neobundle
|
||||
- pathogen
|
||||
|
||||
## FAQ
|
||||
|
||||
- matchup doesn't work
|
||||
|
||||
The plugin requires a recent version of vim. Please tell me your vim
|
||||
version and error messages. Try updating vim and see if the problem
|
||||
persists.
|
||||
|
||||
- why does jumping not work for construct X in language Y?
|
||||
|
||||
Please open a new issue
|
||||
|
||||
- highlighting is not correct for construct X
|
||||
|
||||
matchup uses matchit's filetype data, which may not give enough
|
||||
information to create proper highlights. To fix this, you may add a
|
||||
highlight quirk.
|
||||
|
||||
For help, please open a new issue and be a specific as possible.
|
||||
|
||||
- how can I contribute?
|
||||
- I'm having performance problems
|
||||
|
||||
matchup aims to be as fast as possible. If you see any performance
|
||||
issues, please open a new issue and report `g:matchup_times`.
|
||||
|
||||
## Interoperability
|
||||
|
||||
- Conflicts with end-wise
|
||||
- matchit.vim should not be loaded. If it is loaded, it must be loaded
|
||||
before matchup.
|
||||
|
||||
## acknowledgments
|
||||
|
||||
### origin
|
||||
|
||||
matchup was originally based on [@lervag](https://github.com/lervag/)'s
|
||||
[vimtex](github.com/lervag/vimtex).
|
||||
|
||||
### inspiration
|
||||
|
||||
* [matchit]().
|
||||
* [matchparen]().
|
||||
* [vim-endwise](https://github.com/tpope/vim-endwise).
|
||||
|
||||
## license
|
||||
|
||||
Totally new features
|
||||
|
||||
- parallel transformations (transmutation)
|
||||
(need to cache matches and see if they change)
|
||||
- polymorphic / smart -> if:end,while:end
|
||||
- native split/join
|
||||
- quirks
|
||||
- auto insert
|
||||
|
||||
Definitions
|
||||
|
||||
Matchword
|
||||
A matchword is an regular expression which defines interesting items
|
||||
to matchup matchup treats specially. For instance, by default ( and ) are
|
||||
paired matchwords.
|
||||
is on the matched to buffer text,
|
||||
becomes a matched word,
|
||||
|
||||
Matched word
|
||||
A matched word is an instance of buffer text which matches
|
||||
|
||||
Variables
|
||||
|
||||
matchup understands the following variables
|
||||
b:match_words a set of
|
||||
b:match_ignorecase
|
||||
b:match_skip
|
||||
loaded_matchit
|
||||
|
||||
Existing matchit features, made better:
|
||||
|
||||
% v_% between matches
|
||||
g% v_g% backwards between matches
|
||||
[% ]% to nearest unmatched
|
||||
o_a% o_i% delimited text object
|
||||
|
||||
Features in matchparen:
|
||||
|
||||
matchup emulates matchparen's highlighting for matchpairs
|
||||
Echo invisible pairs
|
||||
|
||||
Features not in matchit:
|
||||
|
||||
Auto-completion
|
||||
ctrl-x <CR> shift <CR>
|
||||
completes the nearest unmatched matchword. When n-tuple matchwords are used
|
||||
the last one is inserted.
|
||||
|
||||
Highlighting general
|
||||
matchup highlights matches for b:match_words
|
||||
|
||||
Jump into
|
||||
z[]% go to the center of the next group of matchwords
|
||||
|
||||
Development
|
||||
|
||||
## TODO
|
||||
|
||||
- Screenshots
|
||||
|
||||
|
||||
|
||||
78
autoload/matchup.vim
Normal file
78
autoload/matchup.vim
Normal file
@@ -0,0 +1,78 @@
|
||||
|
||||
function! matchup#init()
|
||||
call s:init_options()
|
||||
call s:init_modules()
|
||||
call s:init_default_mappings()
|
||||
endfunction
|
||||
|
||||
function! s:init_options()
|
||||
call s:init_option('matchup_matchparen_enabled', 1)
|
||||
|
||||
call s:init_option('matchup_motion_enabled', 1)
|
||||
call s:init_option('matchup_motion_close_end', 1) " xxx to implement
|
||||
|
||||
call s:init_option('matchup_text_obj_enabled', 1)
|
||||
call s:init_option('matchup_text_obj_linewise_operators', ['d', 'y'])
|
||||
|
||||
call s:init_option('matchup_imap_enabled', 1)
|
||||
endfunction
|
||||
|
||||
function! s:init_option(option, default)
|
||||
let l:option = 'g:' . a:option
|
||||
if !exists(l:option)
|
||||
let {l:option} = a:default
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:init_modules()
|
||||
for l:mod in s:modules
|
||||
if index(get(g:, 'matchup_disabled_modules', []), l:mod) >= 0
|
||||
continue
|
||||
endif
|
||||
|
||||
try
|
||||
call matchup#{l:mod}#init_module()
|
||||
catch /E117.*#init_/
|
||||
endtry
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:init_default_mappings()
|
||||
if !get(g:,'matchup_mappings_enabled', 1) | return | endif
|
||||
|
||||
function! s:map(mode, lhs, rhs, ...)
|
||||
if !hasmapto(a:rhs, a:mode)
|
||||
\ && ((a:0 > 0) || (maparg(a:lhs, a:mode) ==# ''))
|
||||
silent execute a:mode . 'map ' a:lhs a:rhs
|
||||
" <silent> XXX
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" these are forced in order to overwrite matchit mappings
|
||||
if get(g:, 'matchup_motion_enabled', 0)
|
||||
call s:map('n', '%', '<plug>(matchup-%)', 1)
|
||||
call s:map('n', 'g%', '<plug>(matchup-g%)', 1)
|
||||
call s:map('n', ']%', '<plug>(matchup-]%)', 1)
|
||||
call s:map('n', '[%', '<plug>(matchup-[%)', 1)
|
||||
|
||||
call s:map('x', '%', '<plug>(matchup-%)', 1)
|
||||
call s:map('o', '%', '<plug>(matchup-%)', 1)
|
||||
endif
|
||||
|
||||
if get(g:, 'matchup_text_obj_enabled', 0)
|
||||
call s:map('x', 'i%', '<plug>(matchup-i%)')
|
||||
call s:map('x', 'a%', '<plug>(matchup-a%)')
|
||||
call s:map('o', 'i%', '<plug>(matchup-i%)')
|
||||
call s:map('o', 'a%', '<plug>(matchup-a%)')
|
||||
endif
|
||||
|
||||
if get(g:, 'matchup_imap_enabled', 0)
|
||||
call s:map('i', '<c-x><cr>', '<plug>(matchup-delim-close)')
|
||||
endif
|
||||
endfunction
|
||||
|
||||
let s:modules = map(
|
||||
\ glob(fnamemodify(expand('<sfile>'), ':r') . '/*.vim', 0, 1),
|
||||
\ 'fnamemodify(v:val, '':t:r'')')
|
||||
|
||||
" vim: fdm=marker sw=2
|
||||
956
autoload/matchup/delim.vim
Normal file
956
autoload/matchup/delim.vim
Normal file
@@ -0,0 +1,956 @@
|
||||
" vim match-up - matchit replacement and more
|
||||
"
|
||||
" Maintainer: Andy Massimino
|
||||
" Email: a@normed.space
|
||||
"
|
||||
|
||||
let s:save_cpo = &cpo
|
||||
set cpo&vim
|
||||
|
||||
function! matchup#delim#init_module() " {{{1
|
||||
|
||||
" nnoremap <silent><buffer> <plug>(matchup-delim-delete)
|
||||
" \ :call matchup#delim#delete()<cr>
|
||||
|
||||
" <silent> XXX
|
||||
inoremap <plug>(matchup-delim-close)
|
||||
\ <c-r>=matchup#delim#close()<cr>
|
||||
|
||||
augroup matchup_filetype
|
||||
au!
|
||||
autocmd FileType * call matchup#delim#init_buffer()
|
||||
augroup END
|
||||
|
||||
call matchup#delim#init_buffer()
|
||||
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#delim#init_buffer() " {{{1
|
||||
" initialize lists of delimiter pairs and regular expressions
|
||||
" this is the data obtained from parsing b:match_words
|
||||
let b:matchup_delim_lists = s:init_delim_lists()
|
||||
|
||||
" this is the combined set of regular expressions used for matching
|
||||
" its structure is matchup_delim_re[type][open,close,both,mid,both_all]
|
||||
let b:matchup_delim_re = s:init_delim_regexes()
|
||||
|
||||
" enable/disable for this buffer
|
||||
let b:matchup_delim_enabled = 1
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
|
||||
function! matchup#delim#close() " {{{1
|
||||
let l:save_pos = matchup#pos#get_cursor()
|
||||
let l:pos_val_cursor = matchup#pos#val(l:save_pos)
|
||||
|
||||
let l:lnum = l:save_pos[1] + 1
|
||||
while l:lnum > 1
|
||||
let l:open = matchup#delim#get_prev('all', 'open',
|
||||
\ { 'syn_exclude' : 'Comment' })
|
||||
if empty(l:open)
|
||||
break
|
||||
endif
|
||||
|
||||
let l:close = matchup#delim#get_matching(l:open)
|
||||
if empty(l:close.match)
|
||||
call matchup#pos#set_cursor(l:save_pos)
|
||||
return l:open.corr
|
||||
endif
|
||||
|
||||
let l:pos_val_try = matchup#pos#val(l:close) + strlen(l:close.match)
|
||||
if l:pos_val_try > l:pos_val_cursor
|
||||
call matchup#pos#set_cursor(l:save_pos)
|
||||
return l:open.corr
|
||||
else
|
||||
let l:lnum = l:open.lnum
|
||||
call matchup#pos#set_cursor(matchup#pos#prev(l:open))
|
||||
endif
|
||||
endwhile
|
||||
|
||||
call matchup#pos#set_cursor(l:save_pos)
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
|
||||
function! matchup#delim#get_next(type, side, ...) " {{{1
|
||||
return s:get_delim(extend({
|
||||
\ 'direction' : 'next',
|
||||
\ 'type' : a:type,
|
||||
\ 'side' : a:side,
|
||||
\}, get(a:, '1', {})))
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#delim#get_prev(type, side, ...) " {{{1
|
||||
return s:get_delim(extend({
|
||||
\ 'direction' : 'prev',
|
||||
\ 'type' : a:type,
|
||||
\ 'side' : a:side,
|
||||
\}, get(a:, '1', {})))
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#delim#get_current(type, side, ...) " {{{1
|
||||
return s:get_delim(extend({
|
||||
\ 'direction' : 'current',
|
||||
\ 'type' : a:type,
|
||||
\ 'side' : a:side,
|
||||
\}, get(a:, '1', {})))
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#delim#get_matching(delim, ...) " {{{1
|
||||
if empty(a:delim) || !has_key(a:delim, 'lnum') | return {} | endif
|
||||
|
||||
"PP a:delim.regextwo
|
||||
|
||||
" get the matching position(s)
|
||||
let l:matches = []
|
||||
for l:down in {'open': [1], 'close': [0], 'mid': [0,1]}[a:delim.side]
|
||||
let l:save_pos = matchup#pos#get_cursor()
|
||||
call matchup#pos#set_cursor(a:delim)
|
||||
if !empty(l:matches)
|
||||
call add(l:matches, [])
|
||||
endif
|
||||
call extend(l:matches, a:delim.get_matching(l:down))
|
||||
call matchup#pos#set_cursor(l:save_pos)
|
||||
endfor
|
||||
|
||||
if a:delim.side ==# 'open'
|
||||
call insert(l:matches, [])
|
||||
endif
|
||||
if a:delim.side ==# 'close'
|
||||
call add(l:matches, [])
|
||||
endif
|
||||
|
||||
" echo '$' l:matches
|
||||
|
||||
" create the match result(s)
|
||||
let l:matching_list = []
|
||||
for l:i in range(len(l:matches))
|
||||
if empty(l:matches[l:i])
|
||||
call add(l:matching_list, a:delim)
|
||||
continue
|
||||
end
|
||||
|
||||
let [l:match, l:lnum, l:cnum] = l:matches[l:i]
|
||||
|
||||
let l:matching = deepcopy(a:delim)
|
||||
let l:matching.lnum = l:lnum
|
||||
let l:matching.cnum = l:cnum
|
||||
let l:matching.match = l:match
|
||||
let l:matching.side = l:i == 0 ? 'open'
|
||||
\ : l:i == len(l:matches)-1 ? 'close' : 'mid'
|
||||
let l:matching.corr = a:delim.match
|
||||
let l:matching.rematch = a:delim.regextwo[l:matching.side]
|
||||
|
||||
" defunct, remove
|
||||
let l:matching.is_open = !a:delim.is_open
|
||||
" let l:matching.re.corr = a:delim.re.this
|
||||
" let l:matching.re.this = a:delim.re.mids
|
||||
if l:matching.type ==# 'delim'
|
||||
" let l:matching.corr_delim = a:delim.delim
|
||||
" let l:matching.corr_mod = a:delim.mod
|
||||
" let l:matching.delim = a:delim.corr_delim
|
||||
else
|
||||
endif
|
||||
|
||||
call add(l:matching_list, l:matching)
|
||||
endfor
|
||||
|
||||
" PP l:matching_list
|
||||
|
||||
for l:i in range(len(l:matching_list))
|
||||
let l:c = l:matching_list[l:i]
|
||||
if !has_key(l:c, 'links')
|
||||
let l:c.links = {}
|
||||
endif
|
||||
let l:c.links.next = l:matching_list[(l:i+1) % len(l:matching_list)]
|
||||
let l:c.links.prev = l:matching_list[l:i-1]
|
||||
let l:c.links.open = l:matching_list[0]
|
||||
let l:c.links.close = l:matching_list[-1]
|
||||
endfor
|
||||
|
||||
if a:0
|
||||
return l:matching_list
|
||||
else
|
||||
" return a:delim.links.next
|
||||
return a:delim.side ==# 'open' ? l:matching_list[-1]
|
||||
\ : l:matching_list[0]
|
||||
endif
|
||||
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#delim#get_surrounding(type) " {{{1
|
||||
let l:save_pos = matchup#pos#get_cursor()
|
||||
let l:pos_val_cursor = matchup#pos#val(l:save_pos)
|
||||
let l:pos_val_last = l:pos_val_cursor
|
||||
let l:pos_val_open = l:pos_val_cursor - 1
|
||||
|
||||
while l:pos_val_open < l:pos_val_last
|
||||
let l:open = matchup#delim#get_prev(a:type, 'open')
|
||||
if empty(l:open) | break | endif
|
||||
" echo l:open.lnum l:open.cnum
|
||||
let l:close = matchup#delim#get_matching(l:open)
|
||||
let l:pos_val_try = matchup#pos#val(l:close)
|
||||
\ + strdisplaywidth(l:close.match) - 1
|
||||
if l:pos_val_try >= l:pos_val_cursor
|
||||
call matchup#pos#set_cursor(l:save_pos)
|
||||
return [l:open, l:close]
|
||||
else
|
||||
call matchup#pos#set_cursor(matchup#pos#prev(l:open))
|
||||
let l:pos_val_last = l:pos_val_open
|
||||
let l:pos_val_open = matchup#pos#val(l:open)
|
||||
endif
|
||||
endwhile
|
||||
|
||||
call matchup#pos#set_cursor(l:save_pos)
|
||||
return [{}, {}]
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
|
||||
function! s:get_delim(opts) " {{{1
|
||||
" Arguments: {{{2
|
||||
" opts = {
|
||||
" 'direction' : 'next' | 'prev' | 'current'
|
||||
" 'type' : 'delim_tex'
|
||||
" | 'delim_all'
|
||||
" | 'all'
|
||||
" 'side' : 'open'
|
||||
" | 'close'
|
||||
" | 'both'
|
||||
" | 'mid'
|
||||
" | 'both_all'
|
||||
" 'syn_exclude' : don't match in given syntax
|
||||
" }
|
||||
"
|
||||
" }}}2
|
||||
" Returns: {{{2
|
||||
" delim = {
|
||||
" type : 'delim'
|
||||
" lnum : line number
|
||||
" cnum : column number
|
||||
" match : the actual text match
|
||||
" side : 'open' | 'close' | 'mid'
|
||||
" regex : regular expression which matched
|
||||
" regextwo : regular expressions for corresponding
|
||||
" }
|
||||
"
|
||||
" }}}2
|
||||
|
||||
if !get(b:, 'matchup_delim_enabled', 0)
|
||||
return {}
|
||||
endif
|
||||
|
||||
let l:time_start = reltime()
|
||||
|
||||
" if col('.') < indent(line('.'))
|
||||
" let l:elapsed_time = 1000*reltimefloat(reltime(l:time_start))
|
||||
" echo 'nothing' l:elapsed_time
|
||||
" endif
|
||||
|
||||
let l:save_pos = matchup#pos#get_cursor()
|
||||
|
||||
" this contains all the patterns for the specified type and side
|
||||
let l:re = b:matchup_delim_re[a:opts.type][a:opts.side]
|
||||
|
||||
let l:cursorpos = col('.') - (mode() ==# 'i' ? 1 : 0)
|
||||
let l:re .= '\%>'.(l:cursorpos).'c'
|
||||
" let l:re .= '\%>'.(col('.')).'c'
|
||||
" let g:re = l:re
|
||||
|
||||
" use the 'c' cpo flag to allow overlapping matches
|
||||
let l:save_cpo = &cpo
|
||||
noautocmd set cpo-=c
|
||||
|
||||
" in the first pass, we get matching line and column numbers
|
||||
" this is intended to be as fast as possible, with no capture groups
|
||||
" we look for a match on this line (if direction == current)
|
||||
" or forwards or backwards (if direction == next or prev)
|
||||
" for current, we actually search leftwards from the cursor
|
||||
while 1
|
||||
let [l:lnum, l:cnum] = a:opts.direction ==# 'next'
|
||||
\ ? searchpos(l:re, 'cnW', line('.') + s:stopline)
|
||||
\ : a:opts.direction ==# 'prev'
|
||||
\ ? searchpos(l:re, 'bcnW', max([line('.') - s:stopline, 1]))
|
||||
\ : searchpos(l:re, 'bcnW', line('.'))
|
||||
if l:lnum == 0 | break | endif
|
||||
|
||||
" if invalid match, move cursor and keep looking
|
||||
" TODO this function should never be called
|
||||
" in 'current' mode, but we should be more explicit
|
||||
if matchup#util#in_comment(l:lnum, l:cnum)
|
||||
\ || matchup#util#in_string(l:lnum, l:cnum)
|
||||
|
||||
" TODO support next too
|
||||
call matchup#pos#set_cursor(matchup#pos#prev(l:lnum, l:cnum))
|
||||
continue
|
||||
endif
|
||||
|
||||
" TODO support b:match_skip, syn_exclude
|
||||
" if has_key(a:opts, 'syn_exclude')
|
||||
" \ && matchup#util#in_syntax(a:opts.syn_exclude, l:lnum, l:cnum)
|
||||
" call matchup#pos#set_cursor(matchup#pos#prev(l:lnum, l:cnum))
|
||||
" continue
|
||||
" endif
|
||||
" we prefer matches containing the cursor
|
||||
" loop through all the
|
||||
|
||||
break
|
||||
endwhile
|
||||
|
||||
" restore cpo
|
||||
" note: this messes with cursor position
|
||||
noautocmd let &cpo = l:save_cpo
|
||||
|
||||
" restore cursor
|
||||
call matchup#pos#set_cursor(l:save_pos)
|
||||
|
||||
if l:lnum == 0
|
||||
let l:elapsed_time = 1000*reltimefloat(reltime(l:time_start))
|
||||
echo 'X' l:elapsed_time
|
||||
" v:vim_did_enter
|
||||
endif
|
||||
|
||||
" nothing found, leave now
|
||||
if l:lnum == 0 | return {} | endif
|
||||
|
||||
" now we get more data about the match in this position
|
||||
" there may be capture groups which need to be stored
|
||||
|
||||
" result stub, to be filled by the parser when there is a match
|
||||
let l:result = {
|
||||
\ 'type' : '',
|
||||
\ 'lnum' : l:lnum,
|
||||
\ 'cnum' : l:cnum,
|
||||
\ 'match' : '',
|
||||
\ 'groups' : '',
|
||||
\ 'side' : '',
|
||||
\ 'is_open' : '',
|
||||
\ 'regexone' : '',
|
||||
\ 'regextwo' : '',
|
||||
\}
|
||||
|
||||
for l:type in s:types[a:opts.type]
|
||||
let l:parser_result = l:type.parser(l:lnum, l:cnum, a:opts)
|
||||
if !empty(l:parser_result)
|
||||
let l:result = extend(l:parser_result, l:result, 'keep')
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
|
||||
" PP l:result
|
||||
return empty(l:result.type) ? {} : l:result
|
||||
|
||||
return {}
|
||||
|
||||
" echo l:result.type
|
||||
" let l:sides = ['open', 'close', 'mid']
|
||||
" for l:rbr in b:matchup_delim_lists[a:opts.type].regex_backref
|
||||
" for l:s in l:sides
|
||||
" " xxx must use matchstrpos and compare column (?)
|
||||
" " echo l:s l:rbr[l:s] l:cnum l:matches
|
||||
" if l:cnum + strdisplaywidth(l:match)
|
||||
" \ + (mode() ==# 'i' ? 1 : 0) > col('.')
|
||||
" let l:found = 1
|
||||
" endif
|
||||
|
||||
" if l:found | break | endif
|
||||
" endfor
|
||||
" if l:found | break | endif
|
||||
" endfor
|
||||
|
||||
" if l:found
|
||||
" " echo l:matches | sleep 200m
|
||||
" endif
|
||||
" endif
|
||||
|
||||
" return {}
|
||||
|
||||
" let l:realside = l:line =~# b:matchup_delim_re[a:opts.type].open
|
||||
" \ ? 'open'
|
||||
" \ : l:line =~# b:matchup_delim_re[a:opts.type].close
|
||||
" \ ? 'close'
|
||||
" \ : 'mid'
|
||||
" let l:idx = s:parser_delim_find_regexp(getline(l:lnum), l:realside)
|
||||
" echo l:realside l:lnum l:line l:idx | sleep 100m
|
||||
|
||||
" " echo l:reb[l:s] | sleep 500m
|
||||
" endfor
|
||||
" endfor
|
||||
" echo l:idx
|
||||
|
||||
" let l:matches = matchlist(getline(l:lnum), '^' . l:re, l:cnum-1)
|
||||
" let l:match = l:matches[0]
|
||||
" echo l:re l:lnum l:cnum-1
|
||||
|
||||
" let l:match = matchstr(getline(l:lnum), '^' . l:re, l:cnum-1)
|
||||
let l:match = l:matches[0]
|
||||
|
||||
" echo l:lnum l:cnum-1 l:match
|
||||
|
||||
" check that the cursor is inside the match
|
||||
if a:opts.direction ==# 'current'
|
||||
\ && l:cnum + strdisplaywidth(l:match)
|
||||
\ + (mode() ==# 'i' ? 1 : 0) <= col('.')
|
||||
let l:match = ''
|
||||
let l:lnum = 0
|
||||
let l:cnum = 0
|
||||
endif
|
||||
|
||||
|
||||
" get some more info about the match
|
||||
" the parser figures out what side the match it was
|
||||
let l:types = [
|
||||
\ {
|
||||
\ 'regex' : b:matchup_delim_re.delim_all.both_all,
|
||||
\ 'parser' : function('s:parser_delim'),
|
||||
\ },
|
||||
\]
|
||||
for l:type in l:types
|
||||
if l:match =~# '^' . l:type.regex
|
||||
let l:result = extend(
|
||||
\ l:type.parser(l:match, l:lnum, l:cnum,
|
||||
\ a:opts.side, a:opts.type, a:opts.direction),
|
||||
\ l:result, 'keep')
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
|
||||
return empty(l:result.type) ? {} : l:result
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
|
||||
function! s:parser_delim_new(lnum, cnum, opts) " {{{1
|
||||
let l:time_start = reltime()
|
||||
|
||||
let l:cursorpos = col('.') - (mode() ==# 'i' ? 1 : 0)
|
||||
|
||||
if a:opts.direction ==# 'current'
|
||||
let l:found = 0
|
||||
|
||||
let l:sides = s:sidedict[a:opts.side]
|
||||
let l:rebrs = b:matchup_delim_lists[a:opts.type].regex_backref
|
||||
|
||||
" loop through all (index, side) pairs match pairs,
|
||||
" finding the first match which the cursor is inside
|
||||
let l:ns = len(l:sides)
|
||||
let l:found = 0
|
||||
for l:i in range(len(l:rebrs)*l:ns)
|
||||
let l:side = l:sides[ l:i % l:ns ]
|
||||
let l:re = l:rebrs[ l:i / l:ns ][l:side]
|
||||
if empty(l:re) | continue | end
|
||||
|
||||
" prepend the column number and append
|
||||
" the cursor column to anchor match
|
||||
" we don't use {start} for matchlist because there may
|
||||
" be zero-width look behinds
|
||||
" XXX does \%<Nc work properly with tabs?
|
||||
" let l:re = '\%'.l:cnum.'c\%(' . l:re .'\)'
|
||||
" \ . '\%>'.(col('.')).'c'
|
||||
let l:re2 = '\%'.a:cnum.'c\%(' . l:re .'\)'
|
||||
\ . '\%>'.(l:cursorpos).'c'
|
||||
" xxx is this index right?
|
||||
|
||||
" echo l:re | sleep 6
|
||||
|
||||
let l:matches = matchlist(getline(a:lnum), l:re2)
|
||||
|
||||
if empty(l:matches) | continue | endif
|
||||
|
||||
let l:match = l:matches[0]
|
||||
|
||||
" echo localtime() l:re l:matches 'lc' l:lnum l:cnum
|
||||
" \ l:cnum+strdisplaywidth(l:match) col('.') | sleep 1
|
||||
|
||||
let l:found = 1
|
||||
break
|
||||
endfor
|
||||
|
||||
let l:elapsed_time = 1000*reltimefloat(reltime(l:time_start))
|
||||
|
||||
if l:found
|
||||
let l:list = b:matchup_delim_lists[a:opts.type]
|
||||
let l:result = {
|
||||
\ 'type' : 'delim',
|
||||
\ 'match' : l:match,
|
||||
\ 'groups' : l:matches,
|
||||
\ 'side' : l:side,
|
||||
\ 'is_open' : (l:side ==# 'open') ? 1 : 0,
|
||||
\ 'get_matching' : function('s:get_matching_delims'),
|
||||
\ 'regexone' : l:list.regex[l:i / l:ns],
|
||||
\ 'regextwo' : l:list.regex_backref[l:i / l:ns],
|
||||
\ 'rematch' : l:re,
|
||||
\}
|
||||
|
||||
"echo l:matches 'lc' a:lnum a:cnum l:elapsed_time
|
||||
endif
|
||||
|
||||
if !l:found
|
||||
echo l:elapsed_time
|
||||
return {}
|
||||
endif
|
||||
endif
|
||||
|
||||
return l:result
|
||||
endfunction
|
||||
" }}}1
|
||||
|
||||
function! s:parser_delim(match, lnum, cnum, ...) " {{{1
|
||||
let result = {}
|
||||
let result.type = 'delim'
|
||||
let result.side = a:match =~# b:matchup_delim_re.delim_all.open
|
||||
\ ? 'open'
|
||||
\ : a:match =~# b:matchup_delim_re.delim_all.close
|
||||
\ ? 'close'
|
||||
\ : 'mid'
|
||||
let result.get_matching = function('s:get_matching_delims')
|
||||
|
||||
let result.is_open = result.side ==# 'open' " xxx remove
|
||||
|
||||
let l:type = 'delim_all'
|
||||
|
||||
" find corresponding delimiter and the regexps
|
||||
let d1 = a:match
|
||||
|
||||
let l:idx = s:parser_delim_find_regexp(a:match, result.side)
|
||||
let l:re1 = b:matchup_delim_lists[l:type].regex[l:idx][result.side]
|
||||
|
||||
let l:rex = b:matchup_delim_lists[l:type].regex[l:idx]
|
||||
" echo l:result.side l:rex
|
||||
|
||||
" let l:re1 = b:matchup_delim_lists[l:type].re[l:idx][result.is_open ? 0 : -1]
|
||||
|
||||
" echo l:idx l:re1
|
||||
" let [re1, idx] = s:parser_delim_get_regexp(a:match, result.is_open ? 0 : -1)
|
||||
|
||||
" let d2 = s:parser_delim_get_corr(a:match)
|
||||
" let [re2, idx] = s:parser_delim_get_regexp(d2, result.is_open ? -1 : 0)
|
||||
|
||||
" ending delimiter *DEPRECATE THIS
|
||||
let d2 = b:matchup_delim_lists[l:type].name[l:idx][result.is_open ? -1 : 0]
|
||||
let re2 = b:matchup_delim_lists[l:type].re[l:idx][result.is_open ? -1 : 0]
|
||||
|
||||
" middle set
|
||||
let d3 = b:matchup_delim_lists[l:type].name[l:idx][1:-2]
|
||||
let re3 = join(b:matchup_delim_lists[l:type].re[l:idx][1:-2], '\|')
|
||||
|
||||
" echo 'd1' d1 're1' re1 'd2' d2 're2' re2 | sleep 400m
|
||||
|
||||
let result.regex = re1
|
||||
let result.regextwo = b:matchup_delim_lists[l:type].regex[l:idx]
|
||||
|
||||
" xxx we really don't need the rest of these
|
||||
" let result.links = {
|
||||
" \ 'open' : {},
|
||||
" \ 'prev' : {},
|
||||
" \ 'next' : {},
|
||||
" \ 'close' : {},
|
||||
" \}
|
||||
let result.delim = d1
|
||||
let result.mod = '' " xxx defunct
|
||||
let result.corr = 'FIXME2'
|
||||
let result.corr_delim = d2
|
||||
let result.corr_mod = '' " xxx defunct
|
||||
let result.mids_ = 'FIXME3' " xxx unused?
|
||||
let result.regextwo.this = re1
|
||||
let result.re = {
|
||||
\ 'this' : re1,
|
||||
\ 'corr' : re2,
|
||||
\ 'open' : result.is_open ? re1 : re2,
|
||||
\ 'close' : result.is_open ? re2 : re1,
|
||||
\ 'mids' : re3,
|
||||
\}
|
||||
|
||||
return result
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! s:parser_delim_find_regexp(delim, side, ...) " {{{1
|
||||
let l:type = a:0 > 0 ? a:1 : 'delim_all'
|
||||
|
||||
let l:index = index(map(copy(b:matchup_delim_lists[l:type].regex),
|
||||
\ 'a:delim =~# v:val.' . a:side), 1)
|
||||
|
||||
return l:index
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! s:parser_delim_get_regexp(delim, side, ...) " {{{1
|
||||
" DEPRECATED REMOVE
|
||||
let l:type = a:0 > 0 ? a:1 : 'delim_all'
|
||||
|
||||
let l:index = index(map(copy(b:matchup_delim_lists[l:type].re),
|
||||
\ 'a:delim =~# v:val[' . a:side . ']'), 1)
|
||||
|
||||
return [l:index >= 0
|
||||
\ ? b:matchup_delim_lists[l:type].re[l:index][a:side]
|
||||
\ : '', l:index]
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! s:parser_delim_get_corr(delim, ...) " {{{1
|
||||
let l:type = a:0 > 0 ? a:1 : 'delim_all'
|
||||
|
||||
for l:pair in b:matchup_delim_lists[l:type].re
|
||||
if a:delim =~# l:pair[0]
|
||||
return l:pair[-1]
|
||||
elseif a:delim =~# l:pair[-1]
|
||||
return l:pair[0]
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
|
||||
function! s:get_matching_delims(down) dict " {{{1
|
||||
let [l:re, l:flags, l:stopline] = a:down
|
||||
\ ? [self.regextwo.close, 'zW', line('.') + s:stopline]
|
||||
\ : [self.regextwo.open, 'zbW', max([line('.') - s:stopline, 1])]
|
||||
|
||||
" echo self.side
|
||||
|
||||
" return [['', 0, 0]]
|
||||
|
||||
" XXX
|
||||
let l:skip = 'matchup#util#in_comment() || matchup#util#in_string()'
|
||||
|
||||
" remove capture groups
|
||||
" xxx spin off function
|
||||
let l:sub_grp = '\(\\\@<!\(\\\\\)*\)\@<=\\('
|
||||
let l:open = substitute(self.regextwo.open, l:sub_grp, '\\%(', 'g')
|
||||
let l:close = substitute(self.regextwo.close, l:sub_grp, '\\%(', 'g')
|
||||
let l:mids = substitute(self.regextwo.mid, l:sub_grp, '\\%(', 'g')
|
||||
|
||||
" insert captured groups
|
||||
" XXX do this
|
||||
|
||||
" this is the corresponding of an open:close pair
|
||||
let [l:lnum_corr, l:cnum_corr] = searchpairpos(l:open, '', l:close,
|
||||
\ 'n'.l:flags, l:skip, l:stopline)
|
||||
|
||||
let l:match = matchstr(getline(l:lnum_corr), '^' . l:re, l:cnum_corr-1)
|
||||
|
||||
" l:re might have back references
|
||||
" let l:match = l:matches[0]
|
||||
|
||||
" echo self.regextwo
|
||||
" echo l:open l:close
|
||||
" echo a:down ? 'down' : 'up' l:lnum_corr l:cnum_corr l:match
|
||||
|
||||
if empty(l:mids)
|
||||
return [[l:match, l:lnum_corr, l:cnum_corr]]
|
||||
endif
|
||||
|
||||
let l:re .= '\|'.l:mids
|
||||
|
||||
" echo l:re
|
||||
|
||||
let l:list = []
|
||||
while 1
|
||||
let [l:lnum, l:cnum] = searchpairpos(l:open, l:mids, l:close,
|
||||
\ l:flags, l:skip, l:lnum_corr)
|
||||
if l:lnum <= 0 | break | endif
|
||||
|
||||
" echo l:lnum l:cnum | sleep 500m
|
||||
if stridx(l:flags, 'b') >= 0
|
||||
if l:lnum < l:lnum_corr && l:cnum < l:cnum_corr | break | endif
|
||||
else
|
||||
if l:lnum > l:lnum_corr && l:cnum > l:cnum_corr | break | endif
|
||||
endif
|
||||
|
||||
" XXX check lnum cnum vs lnum_corr cnum_corr
|
||||
|
||||
let l:match = matchstr(getline(l:lnum), '^' . l:re, l:cnum-1)
|
||||
" echo l:lnum l:match | sleep 1
|
||||
call add(l:list, [l:match, l:lnum, l:cnum])
|
||||
endwhile
|
||||
|
||||
if empty(l:list) | return [['', 0, 0]] | endif
|
||||
|
||||
if !a:down
|
||||
call reverse(l:list)
|
||||
endif
|
||||
|
||||
return l:list
|
||||
endfunction
|
||||
" }}}1
|
||||
|
||||
function! s:get_matching_delim() dict " {{{1
|
||||
let [re, flags, stopline] = self.is_open
|
||||
\ ? [self.re.close, 'nW', line('.') + s:stopline]
|
||||
\ : [self.re.open, 'bnW', max([line('.') - s:stopline, 1])]
|
||||
|
||||
" xxx spin-off
|
||||
let l:open = substitute(self.re.open,
|
||||
\ '\(\\\@<!\(\\\\\)*\)\@<=\\(', '\\%(', 'g')
|
||||
let l:close = substitute(self.re.close,
|
||||
\ '\(\\\@<!\(\\\\\)*\)\@<=\\(', '\\%(', 'g')
|
||||
|
||||
let [lnum, cnum] = searchpairpos(l:open, '', l:close,
|
||||
\ flags, '', stopline)
|
||||
let match = matchstr(getline(lnum), '^' . re, cnum-1)
|
||||
|
||||
return [match, lnum, cnum]
|
||||
endfunction
|
||||
" }}}1
|
||||
|
||||
function! s:init_delim_lists() " {{{1
|
||||
let l:lists = { 'delim_tex': { 'name': [], 're': [],
|
||||
\ 'regex': [], 'regex_backref': [] } }
|
||||
|
||||
" let b:match_words = '\(\(foo\)\(bar\)\):\3\2:end\1'
|
||||
" let b:match_words = '\(foo\)\(bar\):more\1:and\2:end\1\2'
|
||||
|
||||
" parse matchpairs and b:match_words
|
||||
let l:mps = escape(&matchpairs, '[$^.*~\\/?]')
|
||||
let l:match_words = get(b:, 'match_words', '') . ','.l:mps
|
||||
let s:notslash = '\\\@<!\%(\\\\\)*'
|
||||
let l:sets = split(l:match_words, s:notslash.',')
|
||||
|
||||
let l:seen = {}
|
||||
for l:s in l:sets
|
||||
if has_key(l:seen, l:s) | continue | endif
|
||||
let l:seen[l:s] = 1
|
||||
|
||||
let l:words = split(l:s, s:notslash.':')
|
||||
|
||||
" resolve backrefs to produce two sets of words,
|
||||
" one with \(foo\)s and one with \1s
|
||||
" XXX there is a counting problem: when substituting \(\)
|
||||
" must increment the capture groups-it is very subtle.
|
||||
let l:words_backref = copy(l:words)
|
||||
let l:capture_groups = []
|
||||
|
||||
for l:i in range(1, len(l:words)-1)
|
||||
" find the groups like \(foo\) in the previous set of words
|
||||
let l:cg = s:get_delim_capture_groups(l:words_backref[l:i-1])
|
||||
|
||||
" substitute \1 with the found groups
|
||||
let l:words_backref[l:i] = substitute(l:words_backref[l:i],
|
||||
\ s:notslash.'\\'.'\(\d\)',
|
||||
\ '\=get(get(l:cg, submatch(1), {}), "str")', 'g')
|
||||
|
||||
call add(l:capture_groups, l:cg)
|
||||
endfor
|
||||
|
||||
" now replace the original capture groups with equivalent \1
|
||||
function! s:capture_group_sort(cg, a, b) dict
|
||||
return a:cg[a:b].depth - a:cg[a:a].depth
|
||||
endfunction
|
||||
|
||||
for l:i in range(len(l:words)-1)
|
||||
let l:cg = l:capture_groups[l:i]
|
||||
if empty(l:cg) | continue | end
|
||||
|
||||
" this must be done deepest to shallowest
|
||||
let l:order = sort(keys(l:cg), 'n')
|
||||
call sort(l:order, function('s:capture_group_sort', l:cg))
|
||||
for l:j in l:order
|
||||
let l:words[l:i] = strpart(l:words[l:i], 0, l:cg[l:j].pos[0])
|
||||
\ .('\'.l:j).strpart(l:words[l:i], l:cg[l:j].pos[1])
|
||||
endfor
|
||||
endfor
|
||||
|
||||
call add(l:lists.delim_tex.regex, {
|
||||
\ 'open' : l:words[0],
|
||||
\ 'close' : l:words[-1],
|
||||
\ 'mid' : join(l:words[1:-2], '\|'),
|
||||
\ 'mid_list' : l:words[1:-2],
|
||||
\})
|
||||
|
||||
call add(l:lists.delim_tex.regex_backref, {
|
||||
\ 'open' : l:words_backref[0],
|
||||
\ 'close' : l:words_backref[-1],
|
||||
\ 'mid' : join(l:words_backref[1:-2], '\|'),
|
||||
\ 'mid_list' : l:words_backref[1:-2],
|
||||
\ 'capgrps' : l:capture_groups,
|
||||
\})
|
||||
|
||||
call add(l:lists.delim_tex.re, deepcopy(l:words)) " xxx deprecated
|
||||
|
||||
" xxx deprecate
|
||||
call add(l:lists.delim_tex.name,
|
||||
\ map(l:words, '"m_".substitute(v:val, ''\\'', "", "g")'))
|
||||
endfor
|
||||
|
||||
" get user defined lists
|
||||
" call extend(l:lists, get(g:, 'matchup_delim_list', {}))
|
||||
|
||||
" generate corresponding regexes if necessary
|
||||
" for l:type in values(l:lists)
|
||||
" if !has_key(l:type, 're') && has_key(l:type, 'name')
|
||||
" let l:type.re = map(deepcopy(l:type.name),
|
||||
" \ 'map(v:val, ''escape(v:val, ''''\$[]'''')'')')
|
||||
" endif
|
||||
" endfor
|
||||
|
||||
" generate combined lists
|
||||
let l:lists.delim_all = {}
|
||||
let l:lists.all = {}
|
||||
for k in ['name', 're', 'regex', 'regex_backref']
|
||||
let l:lists.delim_all[k] = l:lists.delim_tex[k]
|
||||
let l:lists.all[k] = l:lists.delim_all[k]
|
||||
endfor
|
||||
|
||||
return l:lists
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! s:init_delim_regexes() " {{{1
|
||||
let l:re = {}
|
||||
let l:re.delim_all = {}
|
||||
let l:re.all = {}
|
||||
|
||||
let l:re.delim_tex = s:init_delim_regexes_generator('delim_tex')
|
||||
|
||||
for l:k in ['open', 'close', 'both', 'mid', 'both_all']
|
||||
let l:re.delim_all[l:k] = l:re.delim_tex[l:k]
|
||||
let l:re.all[l:k] = l:re.delim_all[l:k]
|
||||
endfor
|
||||
|
||||
" for l:type in values(l:re)
|
||||
" for l:side in keys(l:type)
|
||||
" endfor
|
||||
|
||||
" be explicit about regex mode (set magic mode)
|
||||
for l:type in values(l:re)
|
||||
for l:side in keys(l:type)
|
||||
let l:type[l:side] = '\m' . l:type[l:side]
|
||||
endfor
|
||||
endfor
|
||||
|
||||
return l:re
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! s:init_delim_regexes_generator(list_name) " {{{1
|
||||
let l:list = b:matchup_delim_lists[a:list_name].regex_backref
|
||||
|
||||
" build the full regex strings: order matters here
|
||||
let l:regexes = {}
|
||||
for [l:key, l:sidelist] in items(s:sidedict)
|
||||
let l:relist = []
|
||||
|
||||
for l:set in l:list
|
||||
for l:side in l:sidelist
|
||||
if strlen(l:set[l:side])
|
||||
call add(l:relist, l:set[l:side])
|
||||
endif
|
||||
endfor
|
||||
endfor
|
||||
|
||||
let l:regexes[l:key] = s:remove_capture_groups(
|
||||
\ '\%(' . join(l:relist, '\|') . '\)')
|
||||
endfor
|
||||
|
||||
" let l:open = join(map(copy(l:list), 'v:val.open'), '\|')
|
||||
" let l:close = join(map(copy(l:list), 'v:val.close'), '\|')
|
||||
" let l:mids = join(filter(map(copy(l:list), 'v:val.mid'),
|
||||
" \ '!empty(v:val)'), '\|')
|
||||
" let l:open = join(map(copy(l:list.re), 'v:val[0]'), '\|')
|
||||
" let l:close = join(map(copy(l:list.re), 'v:val[-1]'), '\|')
|
||||
" let l:mids = map(copy(l:list.re), 'join(v:val[1:-2], ''\|'')')
|
||||
" call filter(l:mids, '!empty(v:val)')
|
||||
" let l:mids = join(l:mids, '\|')
|
||||
|
||||
" \ 'open' : '\%(' . l:open . '\)',
|
||||
" \ 'close' : '\%(' . l:close . '\)',
|
||||
" \ 'both' : '\%(' . l:open . '\|' . l:close . '\)',
|
||||
" \ 'mid' : strlen(l:mids) ? '\%(' . l:mids . '\)' : '',
|
||||
" \}
|
||||
|
||||
" if strlen(l:mids)
|
||||
" let l:regexes.both_all = '\%(' . l:open . '\|' . l:close
|
||||
" \ . '\|' . l:mids . '\)'
|
||||
" else
|
||||
" let l:regexes.both_all = l:regexes.both
|
||||
" endif
|
||||
|
||||
return l:regexes
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! s:get_delim_capture_groups(str) " {{{1
|
||||
let l:pat = s:notslash.'\zs\(\\(\|\\)\)'
|
||||
|
||||
let l:start = 0
|
||||
|
||||
let l:brefs = {}
|
||||
let l:stack = []
|
||||
let l:counter = 0
|
||||
while 1
|
||||
let l:match = matchstrpos(a:str, l:pat, l:start)
|
||||
if l:match[1] < 0 | break | endif
|
||||
let l:start = l:match[2]
|
||||
|
||||
if l:match[0] ==# '\('
|
||||
let l:counter += 1
|
||||
call add(l:stack, l:counter)
|
||||
let l:brefs[l:counter] = {
|
||||
\ 'str': '', 'depth': len(l:stack),
|
||||
\ 'pos': [l:match[1], 0],
|
||||
\}
|
||||
else
|
||||
if empty(l:stack) | break | endif
|
||||
let l:i = remove(l:stack, -1)
|
||||
let l:j = l:brefs[l:i].pos[0]
|
||||
let l:brefs[l:i].str = strpart(a:str, l:j, l:match[2]-l:j)
|
||||
let l:brefs[l:i].pos[1] = l:match[2]
|
||||
endif
|
||||
endwhile
|
||||
|
||||
call filter(l:brefs, 'has_key(v:val, "str")')
|
||||
|
||||
return l:brefs
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
|
||||
function! s:remove_capture_groups(re) "{{{
|
||||
let l:sub_grp = '\(\\\@<!\(\\\\\)*\)\@<=\\('
|
||||
return substitute(a:re, l:sub_grp, '\\%(', 'g')
|
||||
endfunction
|
||||
|
||||
"}}}
|
||||
function! s:mod(i, n) " {{{1
|
||||
return ((a:i % a:n) + a:n) % a:n
|
||||
endfunction
|
||||
" }}}1
|
||||
|
||||
" initialize script variables
|
||||
let s:stopline = get(g:, 'matchup_delim_stopline', 500)
|
||||
let s:notslash = '\\\@<!\%(\\\\\)*'
|
||||
|
||||
" xxx consider using instead
|
||||
let s:not_bslash = '\v%(\\@<!%(\\\\)*)@<='
|
||||
|
||||
let s:sidedict = {
|
||||
\ 'open' : ['open'],
|
||||
\ 'mid' : ['mid'],
|
||||
\ 'close' : ['close'],
|
||||
\ 'both' : ['open', 'close'],
|
||||
\ 'both_all' : ['open', 'close', 'mid'],
|
||||
\}
|
||||
|
||||
let s:basetypes = {
|
||||
\ 'delim_tex': {
|
||||
\ 'parser' : function('s:parser_delim_new'),
|
||||
\ },
|
||||
\}
|
||||
|
||||
let s:types = {
|
||||
\ 'all' : [ s:basetypes.delim_tex ],
|
||||
\ 'delim_all' : [ s:basetypes.delim_tex ],
|
||||
\ 'delim_tex' : [ s:basetypes.delim_tex ],
|
||||
\}
|
||||
|
||||
let &cpo = s:save_cpo
|
||||
|
||||
" vim: fdm=marker sw=2
|
||||
164
autoload/matchup/matchparen.vim
Normal file
164
autoload/matchup/matchparen.vim
Normal file
@@ -0,0 +1,164 @@
|
||||
" vim match-up - matchit replacement and more
|
||||
"
|
||||
" Maintainer: Andy Massimino
|
||||
" Email: a@normed.space
|
||||
"
|
||||
|
||||
function! matchup#matchparen#init_module() " {{{1
|
||||
if !g:matchup_matchparen_enabled | return | endif
|
||||
|
||||
call matchup#matchparen#enable()
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
|
||||
function! matchup#matchparen#enable() " {{{1
|
||||
" vint: -ProhibitAutocmdWithNoGroup
|
||||
|
||||
augroup matchup_matchparen
|
||||
autocmd!
|
||||
autocmd CursorMoved * call s:matchparen.highlight()
|
||||
autocmd CursorMovedI * call s:matchparen.highlight()
|
||||
augroup END
|
||||
|
||||
call s:matchparen.highlight()
|
||||
|
||||
" vint: +ProhibitAutocmdWithNoGroup
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#matchparen#disable() " {{{1
|
||||
call s:matchparen.clear()
|
||||
autocmd! matchup_matchparen
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
|
||||
let s:matchparen = {}
|
||||
|
||||
function! s:matchparen.clear() abort dict " {{{1
|
||||
silent! call matchdelete(w:matchup_match_id1)
|
||||
silent! call matchdelete(w:matchup_match_id2)
|
||||
if exists('w:matchup_match_id_list')
|
||||
for l:id in w:matchup_match_id_list
|
||||
silent! call matchdelete(l:id)
|
||||
endfor
|
||||
unlet! w:matchup_match_id_list
|
||||
endif
|
||||
unlet! w:matchup_match_id1
|
||||
unlet! w:matchup_match_id2
|
||||
|
||||
if exists('w:matchup_oldstatus')
|
||||
let &statusline = w:matchup_oldstatus
|
||||
unlet w:matchup_oldstatus
|
||||
endif
|
||||
endfunction
|
||||
" }}}1
|
||||
|
||||
function! s:matchparen.highlight() abort dict " {{{1
|
||||
let l:time_start = reltime()
|
||||
|
||||
call self.clear()
|
||||
|
||||
if matchup#util#in_comment() || matchup#util#in_string()
|
||||
return
|
||||
endif
|
||||
|
||||
let l:current = matchup#delim#get_current('all', 'both_all')
|
||||
if empty(l:current) | return | endif
|
||||
|
||||
let l:corrlist = matchup#delim#get_matching(l:current, 1)
|
||||
if empty(l:corrlist) | return | endif
|
||||
|
||||
" echo l:corrlist
|
||||
" for l:c in l:corrlist
|
||||
" echom l:c.match
|
||||
" endfor
|
||||
" echo map(l:corrlist, 'get(v:val,"match","")')
|
||||
" PP map(l:corrlist, 'has_key'
|
||||
|
||||
" set up links: assume [open, mid.., close]
|
||||
" let l:open = l:corrlist[0]
|
||||
" let l:close = l:corrlist[-1]
|
||||
|
||||
" let l:toremove = -1
|
||||
" for l:i in range(len(l:corrlist))
|
||||
" let l:c = l:corrlist[l:i]
|
||||
" if empty(l:c)
|
||||
" let l:corrlist[l:i] = l:current
|
||||
" let l:toremove = l:i
|
||||
" else
|
||||
" endif
|
||||
" " open prev next close
|
||||
" endfor
|
||||
" if l:toremove > -1
|
||||
" call remove(l:corrlist, l:toremove)
|
||||
" endif
|
||||
" let l:current.links = l:links
|
||||
|
||||
let l:corresponding = l:corrlist[-1]
|
||||
|
||||
let [l:open, l:close] = l:current.is_open
|
||||
\ ? [l:current, l:corresponding]
|
||||
\ : [l:corresponding, l:current]
|
||||
|
||||
" let l:mids = matchup#delim#get_middle(l:open, l:close)
|
||||
|
||||
" let w:matchup_match_id1 = matchadd('MatchParen',
|
||||
" \ '\%' . l:open.lnum . 'l\%' . l:open.cnum
|
||||
" \ . 'c' . l:open.re.this)
|
||||
" let w:matchup_match_id2 = matchadd('MatchParen',
|
||||
" \ '\%' . l:close.lnum . 'l\%' . l:close.cnum
|
||||
" \ . 'c' . l:close.re.this)
|
||||
|
||||
if l:close.lnum > line('w$')
|
||||
" let w:matchup_oldstatus = &statusline
|
||||
" let &statusline = printf('%'.(&numberwidth-1).'s %s',
|
||||
" \ l:close.lnum, l:close.match)
|
||||
echo printf('%'.(&numberwidth-1).'s %s',
|
||||
\ l:close.lnum, l:close.match)
|
||||
elseif exists('w:matchup_oldstatus')
|
||||
let &statusline = w:matchup_oldstatus
|
||||
unlet w:matchup_oldstatus
|
||||
endif
|
||||
if l:open.lnum < line('w0')
|
||||
if &number
|
||||
let l:nw = max([strlen(line('$')), &numberwidth])
|
||||
echo printf('%'.(l:nw).'s %s', l:open.lnum, l:open.match)
|
||||
else
|
||||
echo l:open.match
|
||||
endif
|
||||
endif
|
||||
|
||||
if !exists('w:matchup_match_id_list')
|
||||
let w:matchup_match_id_list = []
|
||||
elseif
|
||||
endif
|
||||
|
||||
|
||||
" echo map(l:corrlist, 'v:val.lnum." ".v:val.re.this')
|
||||
" echo '^' l:corrlist
|
||||
" echo map(l:corrlist, 'v:val')
|
||||
|
||||
for l:corr in l:corrlist
|
||||
" echo l:corr.lnum l:corr.rematch | sleep 1
|
||||
|
||||
call add(w:matchup_match_id_list, matchadd('MatchParen',
|
||||
\ '\%' . l:corr.lnum . 'l'
|
||||
\ . '\%' . l:corr.cnum . 'c'
|
||||
\ . '\%(' . l:corr.rematch . '\)'))
|
||||
|
||||
" echo \ '\%' . l:corr.lnum . 'l\%' . l:corr.cnum
|
||||
" \ . 'c' . l:corr.re.this))
|
||||
" echo '\%' . l:corr.lnum . 'l\%' . l:corr.cnum
|
||||
" \ . 'c\%(' . l:corr.regex . '\)' | sleep 1
|
||||
endfor
|
||||
|
||||
" echo '\%' . l:open.lnum . 'l\%' . l:open.cnum . 'c' . l:open.re.this
|
||||
" \ '\%' . l:close.lnum . 'l\%' . l:close.cnum . 'c' . l:close.re.this
|
||||
|
||||
let g:matchup_hi_time = 1000*reltimefloat(reltime(l:time_start))
|
||||
endfunction
|
||||
" }}}1
|
||||
|
||||
" vim: fdm=marker sw=2
|
||||
83
autoload/matchup/motion.vim
Normal file
83
autoload/matchup/motion.vim
Normal file
@@ -0,0 +1,83 @@
|
||||
" vim match-up - matchit replacement and more
|
||||
"
|
||||
" Maintainer: Andy Massimino
|
||||
" Email: a@normed.space
|
||||
"
|
||||
|
||||
function! matchup#motion#init_module() " {{{1
|
||||
if !g:matchup_motion_enabled | return | endif
|
||||
|
||||
" Utility map to avoid conflict with "normal" command
|
||||
nnoremap <sid>(v) v
|
||||
nnoremap <sid>(V) V
|
||||
|
||||
" jump between matching pairs
|
||||
" XXX add "forced" omap: dV% (must make v,V,C-V)
|
||||
|
||||
" <silent> XXX
|
||||
" todo make % vi compatible wrt yank (:h quote_number)
|
||||
nnoremap <silent> <plug>(matchup-%)
|
||||
\ :<c-u>call matchup#motion#find_matching_pair(0, 1)<cr>
|
||||
nnoremap <silent> <plug>(matchup-g%)
|
||||
\ :<c-u>call matchup#motion#find_matching_pair(0, 0)<cr>
|
||||
|
||||
xnoremap <sid>(matchup-%)
|
||||
\ :<c-u>call matchup#motion#find_matching_pair(1, 1)<cr>
|
||||
xmap <plug>(matchup-%) <sid>(matchup-%)
|
||||
onoremap <plug>(matchup-%)
|
||||
\ :execute "normal \<sid>(v)\<sid>(matchup-%)"<cr>
|
||||
|
||||
nnoremap <plug>(matchup-]%)
|
||||
\ :<c-u>call matchup#motion#find_unmatched(0, 1)<cr>
|
||||
nnoremap <plug>(matchup-[%)
|
||||
\ :<c-u>call matchup#motion#find_unmatched(0, 0)<cr>
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
|
||||
function! matchup#motion#find_matching_pair(visual, down) " {{{1
|
||||
if v:count
|
||||
exe 'normal!' v:count.'%'
|
||||
return
|
||||
endif
|
||||
|
||||
if a:visual
|
||||
normal! gv
|
||||
endif
|
||||
|
||||
let l:delim = matchup#delim#get_current('all', 'both_all')
|
||||
if empty(l:delim)
|
||||
let l:delim = matchup#delim#get_next('all', 'both_all')
|
||||
if empty(l:delim) | return | endif
|
||||
endif
|
||||
|
||||
let l:matches = matchup#delim#get_matching(l:delim)
|
||||
let l:delim = l:delim.links[a:down ? 'next' : 'prev']
|
||||
if empty(l:delim) | return | endif
|
||||
|
||||
normal! m`
|
||||
call matchup#pos#set_cursor(l:delim.lnum,
|
||||
\ (l:delim.side ==# 'close'
|
||||
\ ? l:delim.cnum + strdisplaywidth(l:delim.match) - 1
|
||||
\ : l:delim.cnum))
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#motion#find_unmatched(visual, down) " {{{1
|
||||
" XXX handle visual
|
||||
|
||||
let [l:open, l:close] = matchup#delim#get_surrounding('delim_all')
|
||||
|
||||
let l:delim = a:down ? l:close : l:open
|
||||
if empty(l:delim) | return | endif
|
||||
|
||||
normal! m`
|
||||
call matchup#pos#set_cursor(l:delim.lnum,
|
||||
\ (l:delim.side ==# 'close'
|
||||
\ ? l:delim.cnum + strdisplaywidth(l:delim.match) - 1
|
||||
\ : l:delim.cnum))
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
|
||||
" vim: fdm=marker sw=2
|
||||
95
autoload/matchup/pos.vim
Normal file
95
autoload/matchup/pos.vim
Normal file
@@ -0,0 +1,95 @@
|
||||
" vim match-up - matchit replacement and more
|
||||
"
|
||||
" Maintainer: Andy Massimino
|
||||
" Email: a@normed.space
|
||||
"
|
||||
|
||||
function! matchup#pos#set_cursor(...) " {{{1
|
||||
call cursor(s:parse_args(a:000))
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#pos#get_cursor() " {{{1
|
||||
return exists('*getcurpos') ? getcurpos() : getpos('.')
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#pos#get_cursor_line() " {{{1
|
||||
let l:pos = matchup#pos#get_cursor()
|
||||
return l:pos[1]
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
|
||||
function! matchup#pos#val(...) " {{{1
|
||||
let [l:lnum, l:cnum; l:rest] = s:parse_args(a:000)
|
||||
|
||||
return 100000*l:lnum + min([l:cnum, 90000])
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#pos#next(...) " {{{1
|
||||
let [l:lnum, l:cnum; l:rest] = s:parse_args(a:000)
|
||||
|
||||
return l:cnum < strlen(getline(l:lnum))
|
||||
\ ? [0, l:lnum, l:cnum+1, 0]
|
||||
\ : [0, l:lnum+1, 1, 0]
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#pos#prev(...) " {{{1
|
||||
let [l:lnum, l:cnum; l:rest] = s:parse_args(a:000)
|
||||
|
||||
return l:cnum > 1
|
||||
\ ? [0, l:lnum, l:cnum-1, 0]
|
||||
\ : [0, max([l:lnum-1, 1]), strlen(getline(l:lnum-1)), 0]
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#pos#larger(pos1, pos2) " {{{1
|
||||
return matchup#pos#val(a:pos1) > matchup#pos#val(a:pos2)
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#pos#equal(p1, p2) " {{{1
|
||||
let l:pos1 = s:parse_args(a:p1)
|
||||
let l:pos2 = s:parse_args(a:p2)
|
||||
return l:pos1[:1] == l:pos2[:1]
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#pos#smaller(pos1, pos2) " {{{1
|
||||
return matchup#pos#val(a:pos1) < matchup#pos#val(a:pos2)
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
|
||||
function! s:parse_args(args) " {{{1
|
||||
"
|
||||
" The arguments should be in one of the following forms (when unpacked):
|
||||
"
|
||||
" [lnum, cnum]
|
||||
" [bufnum, lnum, cnum, ...]
|
||||
" {'lnum' : lnum, 'cnum' : cnum}
|
||||
"
|
||||
|
||||
if len(a:args) > 1
|
||||
return s:parse_args([a:args])
|
||||
elseif len(a:args) == 1
|
||||
if type(a:args[0]) == type({})
|
||||
return [get(a:args[0], 'lnum'), get(a:args[0], 'cnum')]
|
||||
else
|
||||
if len(a:args[0]) == 2
|
||||
return a:args[0]
|
||||
else
|
||||
return a:args[0][1:]
|
||||
endif
|
||||
endif
|
||||
else
|
||||
return a:args
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
|
||||
" vim: fdm=marker sw=2
|
||||
13
autoload/matchup/test.html
Normal file
13
autoload/matchup/test.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title of the document</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
Content of the document......
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
103
autoload/matchup/text_obj.vim
Normal file
103
autoload/matchup/text_obj.vim
Normal file
@@ -0,0 +1,103 @@
|
||||
" vim match-up - matchit replacement and more
|
||||
"
|
||||
" Maintainer: Andy Massimino
|
||||
" Email: a@normed.space
|
||||
"
|
||||
|
||||
function! matchup#text_obj#init_module() " {{{1
|
||||
if !g:matchup_text_obj_enabled | return | endif
|
||||
|
||||
for [l:map, l:name, l:opt] in [
|
||||
\ ['%', 'delimited', 'delim_all'],
|
||||
\]
|
||||
let l:p1 = 'noremap <silent> <plug>(matchup-'
|
||||
let l:p2 = l:map . ') :<c-u>call matchup#text_obj#' . l:name
|
||||
let l:p3 = empty(l:opt) ? ')<cr>' : ',''' . l:opt . ''')<cr>'
|
||||
execute 'x' . l:p1 . 'i' . l:p2 . '(1, 1' . l:p3
|
||||
execute 'x' . l:p1 . 'a' . l:p2 . '(0, 1' . l:p3
|
||||
execute 'o' . l:p1 . 'i' . l:p2 . '(1, 0' . l:p3
|
||||
execute 'o' . l:p1 . 'a' . l:p2 . '(0, 0' . l:p3
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#text_obj#delimited(is_inner, mode, type) " {{{1
|
||||
if a:mode
|
||||
let l:selection = getpos("'<")[1:2] + getpos("'>")[1:2]
|
||||
call matchup#pos#set_cursor(getpos("'>"))
|
||||
endif
|
||||
|
||||
let [l:open, l:close] = matchup#delim#get_surrounding(a:type)
|
||||
if empty(l:open)
|
||||
if a:mode
|
||||
normal! gv
|
||||
endif
|
||||
return
|
||||
endif
|
||||
|
||||
let [l1, c1, l2, c2] = [l:open.lnum, l:open.cnum, l:close.lnum, l:close.cnum]
|
||||
|
||||
" Determine if operator is linewise
|
||||
let l:linewise = index(g:matchup_text_obj_linewise_operators, v:operator) >= 0
|
||||
|
||||
" Adjust the borders
|
||||
if a:is_inner
|
||||
if has_key(l:open, 'env_cmd') && !empty(l:open.env_cmd)
|
||||
let l1 = l:open.env_cmd.pos_end.lnum
|
||||
let c1 = l:open.env_cmd.pos_end.cnum+1
|
||||
else
|
||||
let c1 += len(l:open.match)
|
||||
endif
|
||||
let c2 -= 1
|
||||
|
||||
let l:is_inline = (l2 - l1) > 1
|
||||
\ && match(strpart(getline(l1), c1), '^\s*$') >= 0
|
||||
\ && match(strpart(getline(l2), 0, c2), '^\s*$') >= 0
|
||||
|
||||
if l:is_inline
|
||||
let l1 += 1
|
||||
let c1 = strlen(matchstr(getline(l1), '^\s*')) + 1
|
||||
let l2 -= 1
|
||||
let c2 = strlen(getline(l2))
|
||||
if c2 == 0 && ! l:linewise
|
||||
let l2 -= 1
|
||||
let c2 = len(getline(l2)) + 1
|
||||
endif
|
||||
elseif c2 == 0
|
||||
let l2 -= 1
|
||||
let c2 = len(getline(l2)) + 1
|
||||
endif
|
||||
else
|
||||
let c2 += len(l:close.match) - 1
|
||||
|
||||
" Select next pair if we reached the same selection
|
||||
if a:mode && l:selection == [l1, c1, l2, c2]
|
||||
call matchup#pos#set_cursor(matchup#pos#next([l2, c2]))
|
||||
let [l:open, l:close] = matchup#delim#get_surrounding(a:type)
|
||||
if empty(l:open)
|
||||
normal! gv
|
||||
return
|
||||
endif
|
||||
let [l1, c1, l2, c2] = [l:open.lnum, l:open.cnum,
|
||||
\ l:close.lnum, l:close.cnum + len(l:close.match) - 1]
|
||||
endif
|
||||
|
||||
let l:is_inline = (l2 - l1) > 1
|
||||
\ && match(strpart(getline(l1), 0, c1-1), '^\s*$') >= 0
|
||||
\ && match(strpart(getline(l2), 0, c2), '^\s*$') >= 0
|
||||
endif
|
||||
|
||||
" Determine the select mode
|
||||
let l:select_mode = l:is_inline && l:linewise ? 'V'
|
||||
\ : (v:operator ==# ':') ? visualmode() : 'v'
|
||||
|
||||
" Apply selection
|
||||
execute 'normal!' l:select_mode
|
||||
call matchup#pos#set_cursor(l1, c1)
|
||||
normal! o
|
||||
call matchup#pos#set_cursor(l2, c2)
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
|
||||
" vim: fdm=marker sw=2
|
||||
111
autoload/matchup/util.vim
Normal file
111
autoload/matchup/util.vim
Normal file
@@ -0,0 +1,111 @@
|
||||
" vim match-up - matchit replacement and more
|
||||
"
|
||||
" Maintainer: Andy Massimino
|
||||
" Email: a@normed.space
|
||||
"
|
||||
|
||||
function! matchup#util#command(cmd) " {{{1
|
||||
let l:a = @a
|
||||
try
|
||||
silent! redir @a
|
||||
silent! execute a:cmd
|
||||
redir END
|
||||
finally
|
||||
let l:res = @a
|
||||
let @a = l:a
|
||||
return split(l:res, "\n")
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#util#shellescape(cmd) " {{{1
|
||||
"
|
||||
" Path used in "cmd" only needs to be enclosed by double quotes.
|
||||
" shellescape() on Windows with "shellslash" set will produce a path
|
||||
" enclosed by single quotes, which "cmd" does not recognize and reports an
|
||||
" error.
|
||||
"
|
||||
if has('win32')
|
||||
let l:shellslash = &shellslash
|
||||
set noshellslash
|
||||
let l:cmd = escape(shellescape(a:cmd), '\')
|
||||
let &shellslash = l:shellslash
|
||||
return l:cmd
|
||||
else
|
||||
return escape(shellescape(a:cmd), '\')
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#util#get_os() " {{{1
|
||||
if has('win32')
|
||||
return 'win'
|
||||
elseif has('unix')
|
||||
if system('uname') =~# 'Darwin'
|
||||
return 'mac'
|
||||
else
|
||||
return 'linux'
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#util#in_comment(...) " {{{1
|
||||
return call('matchup#util#in_syntax', ['Comment'] + a:000)
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#util#in_string(...) " {{{1
|
||||
return call('matchup#util#in_syntax', ['String'] + a:000)
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#util#in_syntax(name, ...) " {{{1
|
||||
|
||||
" Usage: matchup#util#in_syntax(name, [line, col])
|
||||
|
||||
" Get position and correct it if necessary
|
||||
let l:pos = a:0 > 0 ? [a:1, a:2] : [line('.'), col('.')]
|
||||
if mode() ==# 'i'
|
||||
let l:pos[1] -= 1
|
||||
endif
|
||||
call map(l:pos, 'max([v:val, 1])')
|
||||
|
||||
" Check syntax at position
|
||||
let l:syn = map(synstack(l:pos[0], l:pos[1]),
|
||||
\ "synIDattr(synIDtrans(v:val), 'name')")
|
||||
return match(l:syn, '^' . a:name) >= 0
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#util#uniq(list) " {{{1
|
||||
if exists('*uniq') | return uniq(a:list) | endif
|
||||
if len(a:list) <= 1 | return a:list | endif
|
||||
|
||||
let l:uniq = [a:list[0]]
|
||||
for l:next in a:list[1:]
|
||||
if l:uniq[-1] != l:next
|
||||
call add(l:uniq, l:next)
|
||||
endif
|
||||
endfor
|
||||
return l:uniq
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
function! matchup#util#uniq_unsorted(list) " {{{1
|
||||
if len(a:list) <= 1 | return a:list | endif
|
||||
|
||||
let l:visited = [a:list[0]]
|
||||
for l:index in reverse(range(1, len(a:list)-1))
|
||||
if index(l:visited, a:list[l:index]) >= 0
|
||||
call remove(a:list, l:index)
|
||||
else
|
||||
call add(l:visited, a:list[l:index])
|
||||
endif
|
||||
endfor
|
||||
return a:list
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
|
||||
" vim: fdm=marker sw=2
|
||||
26
plugin/matchup.vim
Normal file
26
plugin/matchup.vim
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
if !get(g:, 'matchup_enabled', 1)
|
||||
finish
|
||||
endif
|
||||
|
||||
if exists('g:loaded_matchup') || &cp
|
||||
finish
|
||||
endif
|
||||
let g:loaded_matchup = 1
|
||||
|
||||
if exists('g:loaded_matchit')
|
||||
echohl WarningMsg
|
||||
echo 'matchup must be loaded before matchit'
|
||||
echohl NONE
|
||||
finish
|
||||
endif
|
||||
let g:loaded_matchit = 1
|
||||
|
||||
if !exists('g:loaded_matchparen')
|
||||
runtime plugin/matchparen.vim
|
||||
endif
|
||||
au! matchparen
|
||||
|
||||
call matchup#init()
|
||||
|
||||
" vim: fdm=marker sw=2
|
||||
Reference in New Issue
Block a user