Initial commit, very rough state

This commit is contained in:
Andy K. Massimino
2017-10-17 16:26:25 -04:00
commit 59c61f397b
13 changed files with 1959 additions and 0 deletions

57
CONTRIBUTING.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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

View 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

View 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
View 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

View 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>

View 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
View 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
View 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