Regenerate nvim config
This commit is contained in:
2
config/neovim/store/lazy-plugins/vim-sandwich/.gitignore
vendored
Normal file
2
config/neovim/store/lazy-plugins/vim-sandwich/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
doc/tags
|
||||
doc/tags-ja
|
||||
17
config/neovim/store/lazy-plugins/vim-sandwich/.travis.yml
Normal file
17
config/neovim/store/lazy-plugins/vim-sandwich/.travis.yml
Normal file
@ -0,0 +1,17 @@
|
||||
language: generic
|
||||
|
||||
env:
|
||||
- PPA=yes
|
||||
|
||||
install:
|
||||
- if [ x"$PPA" == "xyes" ] ; then sudo add-apt-repository ppa:pi-rho/dev -y; fi
|
||||
- sudo apt-get update -q
|
||||
- sudo apt-get install vim-nox
|
||||
- git clone https://github.com/thinca/vim-themis
|
||||
|
||||
before_script:
|
||||
- vim --version
|
||||
- chmod +x ./test/dot-repeat/test_dot.sh
|
||||
|
||||
script:
|
||||
- ./vim-themis/bin/themis && ./vim-themis/bin/themis test/surround/test-surround.vim && ./test/dot-repeat/test_dot.sh
|
||||
242
config/neovim/store/lazy-plugins/vim-sandwich/README.md
Normal file
242
config/neovim/store/lazy-plugins/vim-sandwich/README.md
Normal file
@ -0,0 +1,242 @@
|
||||
vim-sandwich
|
||||
============
|
||||
[](https://travis-ci.org/machakann/vim-sandwich)
|
||||
[](https://ci.appveyor.com/project/machakann/vim-sandwich/branch/master)
|
||||
|
||||
`sandwich.vim` is a plugin that makes it super easy to work with stuff that comes in pairs, like brackets, quotes, and even HTML or XML tags. You can quickly get rid of them, swap them out, or slap new ones around your text.
|
||||
|
||||
# Examples
|
||||
|
||||
Let's dive into some quick examples. If you're inside a string with double quotes and you hit `sr"'`, you'll swap those double quotes for single quotes.
|
||||
|
||||
"Hello world!" -> 'Hello world!'
|
||||
|
||||
Want to turn that into an HTML tag? Easy, just type `sr'<q>` and watch it transform.
|
||||
|
||||
'Hello world!' -> <q>Hello world!</q>
|
||||
|
||||
To switch it back to double quotes, you'd do `srt"`.
|
||||
|
||||
<q>Hello world!</q> -> "Hello world!"
|
||||
|
||||
To strip away those quotes, just press `sd"`.
|
||||
|
||||
"Hello world!" -> Hello world!
|
||||
|
||||
Say you want to bracket the word "Hello", move your cursor there and press `saiw]`.
|
||||
|
||||
Hello world! -> [Hello] world!
|
||||
|
||||
Fancy braces with some breathing room? Type `sr]{`.
|
||||
|
||||
[Hello] world! -> { Hello } world!
|
||||
|
||||
Wrap the whole line in parentheses with `sasb` or `sas)`.
|
||||
|
||||
{ Hello } world! -> ({ Hello } world!)
|
||||
|
||||
To get back to where you started, just do `sd{sd)`.
|
||||
|
||||
({ Hello } world!) -> Hello world!
|
||||
|
||||
Highlight "Hello" with an HTML emphasis tag by typing `saiw<em>`.
|
||||
|
||||
Hello world! -> <em>Hello</em> world!
|
||||
|
||||
For a bigger change, like wrapping the whole line in a paragraph tag with a class, first select the line with `V` and then apply `S<p class="important">`.
|
||||
|
||||
<em>Hello</em> world! -> <p class="important"><em>Hello</em> world!</p>
|
||||
|
||||
This tool is a game-changer for editing HTML and XML in Vim, which is an area that doesn't have a ton of great tools right now. With vim-sandwich, adding, changing, or removing tag pairs is super simple.
|
||||
|
||||
If you are using [vim-surround](https://github.com/tpope/vim-surround), you can use a preset keymappings similar as it. See [here](https://github.com/machakann/vim-sandwich/wiki/Introduce-vim-surround-keymappings)
|
||||
|
||||
# Design
|
||||
|
||||
This plugin provides functions to add/delete/replace surroundings of a sandwiched text. These functions are implemented genuinely by utilizing operator/textobject framework. Thus their action can be repeated by `.` command without any dependency. It consists of two parts, **operator-sandwich** and **textobj-sandwich**.
|
||||
|
||||
### operator-sandwich
|
||||
A sandwiched text could be resolved into two parts, {surrounding} and {surrounded text}.
|
||||
|
||||
* Add surroundings: mapped to the key sequence `sa`
|
||||
* {surrounded text} ---> {surrounding}{surrounded text}{surrounding}
|
||||
|
||||
* Delete surroundings: mapped to the key sequence `sd`
|
||||
* {surrounding}{surrounded text}{surrounding} ---> {surrounded text}
|
||||
|
||||
* Replace surroundings: mapped to the key sequence `sr`
|
||||
* {surrounding}{surrounded text}{surrounding} ---> {new surrounding}{surrounded text}{new surrounding}
|
||||
|
||||
### textobj-sandwich
|
||||
|
||||
* Search and select a sandwiched text automatically: mapped to the key sequence `ib` and `ab`
|
||||
* Search and select a sandwiched text with query: mapped to the key sequence `is` and `as`
|
||||
|
||||
`ib` and `is` selects {surrounded text}. `ab` and `as` selects {surrounded text} including {surrounding}s.
|
||||
```
|
||||
|<----ib,is---->|
|
||||
{surrounding}{surrounded text}{surrounding}
|
||||
|<-----------------ab,as----------------->|
|
||||
```
|
||||
|
||||
### Configuration
|
||||
The point is that it would be nice to be shared the definitions of {surrounding}s pairs in all kinds of operations. User can freely add new settings to extend the functionality. If `g:sandwich#recipes` was defined, this plugin works with the settings inside. As a first step, it would be better to copy the default settings in `g:sandwich#default_recipes`.
|
||||
```vim
|
||||
let g:sandwich#recipes = deepcopy(g:sandwich#default_recipes)
|
||||
```
|
||||
Each setting, it is called `recipe`, is a set of a definition of {surrounding}s pair and options. The key named `buns` is used for the definition of {surrounding}.
|
||||
```
|
||||
let g:sandwich#recipes += [{'buns': [{surrounding}, {surrounding}], 'option-name1': {value1}, 'option-name2': {value2} ...}]
|
||||
|
||||
For example: {'buns': ['(', ')']}
|
||||
foo ---> (foo)
|
||||
```
|
||||
|
||||
Or there is a different way, use external textobjects to define {surrounding}s from the difference of two textobjects.
|
||||
```
|
||||
let g:sandwich#recipes += [{'external': [{textobj-i}, {textobj-a}], 'option-name1': {value1}, 'option-name2': {value} ...}]
|
||||
|
||||
For example: {'external': ['it', 'at']}
|
||||
<title>foo</title> ---> foo
|
||||
```
|
||||
|
||||
# Features
|
||||
|
||||
### Unique count handling
|
||||
As for the default operators, the possible key input in normal mode is like this.
|
||||
```
|
||||
[count1]{operator}[count2]{textobject}
|
||||
```
|
||||
Default operators do not distinguish `[count1]` and `[count2]` but **operator-sandwich** does. `[count1]` is given for `{operators}` and `[count2]` is given for `{textobject}`.
|
||||
|
||||
### Linewise and blockwise operations
|
||||
|
||||
Operator-sandwich works linewise with the linewise-visual selection and linewise motions.
|
||||
|
||||
```vim
|
||||
" press Vsa(
|
||||
foo ---> (
|
||||
foo
|
||||
)
|
||||
```
|
||||
|
||||
Using `command` option, user can execute vim Ex-commands after an action. For example it can be used to adjust indent automatically.
|
||||
|
||||
```vim
|
||||
let g:sandwich#recipes += [
|
||||
\ {
|
||||
\ 'buns' : ['{', '}'],
|
||||
\ 'motionwise' : ['line'],
|
||||
\ 'kind' : ['add'],
|
||||
\ 'linewise' : 1,
|
||||
\ 'command' : ["'[+1,']-1normal! >>"],
|
||||
\ },
|
||||
\ {
|
||||
\ 'buns' : ['{', '}'],
|
||||
\ 'motionwise' : ['line'],
|
||||
\ 'kind' : ['delete'],
|
||||
\ 'linewise' : 1,
|
||||
\ 'command' : ["'[,']normal! <<"],
|
||||
\ }
|
||||
\ ]
|
||||
|
||||
" press Vsa{
|
||||
foo ---> {
|
||||
foo
|
||||
}
|
||||
|
||||
" press V2jsd
|
||||
{ ---> foo
|
||||
foo
|
||||
}
|
||||
```
|
||||
|
||||
Operator-sandwich also can work blockwise with the blockwise-visual selection and blockwise motions.
|
||||
|
||||
```vim
|
||||
" press <C-v>2j2lsa(
|
||||
foo (foo)
|
||||
bar ---> (bar)
|
||||
baz (baz)
|
||||
```
|
||||
|
||||
There is an option to skip white space `skip_space`, it is valid in default. Empty line is ignored.
|
||||
|
||||
```vim
|
||||
" press <C-v>3j$sa(
|
||||
fooooooo (fooooooo)
|
||||
baaaar ---> (baaaar)
|
||||
|
||||
baaaz (baaaz)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Expression surroundings and regular expression matching
|
||||
|
||||
The option `expr` enables to evaluate surroundings (`buns`) before adding/deleting/replacing surroundings. The following recipe is an simple example to wrap texts by html style tags.
|
||||
|
||||
```vim
|
||||
let g:sandwich#recipes += [
|
||||
\ {
|
||||
\ 'buns' : ['TagInput(1)', 'TagInput(0)'],
|
||||
\ 'expr' : 1,
|
||||
\ 'filetype': ['html'],
|
||||
\ 'kind' : ['add', 'replace'],
|
||||
\ 'action' : ['add'],
|
||||
\ 'input' : ['t'],
|
||||
\ },
|
||||
\ ]
|
||||
|
||||
function! TagInput(is_head) abort
|
||||
if a:is_head
|
||||
let s:TagLast = input('Tag: ')
|
||||
if s:TagLast !=# ''
|
||||
let tag = printf('<%s>', s:TagLast)
|
||||
else
|
||||
throw 'OperatorSandwichCancel'
|
||||
endif
|
||||
else
|
||||
let tag = printf('</%s>', matchstr(s:TagLast, '^\a[^[:blank:]>/]*'))
|
||||
endif
|
||||
return tag
|
||||
endfunction
|
||||
```
|
||||
|
||||
The option `regex` is to regard surroundings (`buns`) as regular expressions to match and delete/replace. The following recipe is an simple example to delete both ends of html tag.
|
||||
|
||||
```vim
|
||||
let g:sandwich#recipes += [
|
||||
\ {
|
||||
\ 'buns' : ['<\a[^[:blank:]>/]*.\{-}>',
|
||||
\ '</\a[^[:blank:]>/]*>'],
|
||||
\ 'regex' : 1,
|
||||
\ 'filetype': ['html'],
|
||||
\ 'nesting' : 1,
|
||||
\ 'input' : ['t'],
|
||||
\ },
|
||||
\ ]
|
||||
```
|
||||
|
||||
However the above example is not so accurate. Instead of the example, there are excellent built-in textobjects `it` and `at`, these external textobjects also can be utilized through `external`.
|
||||
|
||||
```vim
|
||||
let g:sandwich#recipes += [
|
||||
\ {
|
||||
\ 'external': ['it', 'at'],
|
||||
\ 'noremap' : 1,
|
||||
\ 'filetype': ['html'],
|
||||
\ 'input' : ['t'],
|
||||
\ },
|
||||
\ ]
|
||||
```
|
||||
|
||||
### Demo
|
||||

|
||||
|
||||
# Pioneers
|
||||
* [vim-surround](https://github.com/tpope/vim-surround)
|
||||
* [vim-operator-surround](https://github.com/rhysd/vim-operator-surround)
|
||||
* [vim-textobj-multiblock](https://github.com/osyo-manga/vim-textobj-multiblock)
|
||||
* [vim-textobj-anyblock](https://github.com/rhysd/vim-textobj-anyblock)
|
||||
* [vim-textobj-between](https://github.com/thinca/vim-textobj-between)
|
||||
@ -0,0 +1,18 @@
|
||||
if &compatible || exists('b:did_sandwich_initex_ftplugin') || get(g:, 'sandwich_no_initex_ftplugin', 0)
|
||||
finish
|
||||
endif
|
||||
|
||||
runtime macros/sandwich/ftplugin/tex.vim
|
||||
|
||||
augroup sandwich-event-FileType
|
||||
autocmd!
|
||||
execute 'autocmd FileType initex source ' . fnameescape(expand('<sfile>'))
|
||||
augroup END
|
||||
|
||||
let b:did_sandwich_initex_ftplugin = 1
|
||||
if !exists('b:undo_ftplugin')
|
||||
let b:undo_ftplugin = ''
|
||||
else
|
||||
let b:undo_ftplugin .= ' | '
|
||||
endif
|
||||
let b:undo_ftplugin .= 'unlet b:did_sandwich_initex_ftplugin | call sandwich#util#ftrevert("tex")'
|
||||
@ -0,0 +1,21 @@
|
||||
if &compatible || exists('b:did_sandwich_julia_ftplugin') || get(g:, 'sandwich_no_julia_ftplugin', 0)
|
||||
finish
|
||||
endif
|
||||
|
||||
" To include multibyte characters
|
||||
let b:sandwich_magicchar_f_patterns = [
|
||||
\ {
|
||||
\ 'header' : '\%#=2\<[[:upper:][:lower:]_]\k*!\?',
|
||||
\ 'bra' : '(',
|
||||
\ 'ket' : ')',
|
||||
\ 'footer' : '',
|
||||
\ },
|
||||
\ ]
|
||||
|
||||
let b:did_sandwich_julia_ftplugin = 1
|
||||
if !exists('b:undo_ftplugin')
|
||||
let b:undo_ftplugin = ''
|
||||
else
|
||||
let b:undo_ftplugin .= ' | '
|
||||
endif
|
||||
let b:undo_ftplugin .= 'unlet! b:did_sandwich_julia_ftplugin b:sandwich_magicchar_f_patterns'
|
||||
@ -0,0 +1,18 @@
|
||||
if &compatible || exists('b:did_sandwich_plaintex_ftplugin') || get(g:, 'sandwich_no_plaintex_ftplugin', 0)
|
||||
finish
|
||||
endif
|
||||
|
||||
runtime macros/sandwich/ftplugin/tex.vim
|
||||
|
||||
augroup sandwich-event-FileType
|
||||
autocmd!
|
||||
execute 'autocmd FileType plaintex source ' . fnameescape(expand('<sfile>'))
|
||||
augroup END
|
||||
|
||||
let b:did_sandwich_plaintex_ftplugin = 1
|
||||
if !exists('b:undo_ftplugin')
|
||||
let b:undo_ftplugin = ''
|
||||
else
|
||||
let b:undo_ftplugin .= ' | '
|
||||
endif
|
||||
let b:undo_ftplugin .= 'unlet b:did_sandwich_plaintex_ftplugin | call sandwich#util#ftrevert("tex")'
|
||||
@ -0,0 +1,18 @@
|
||||
if &compatible || exists('b:did_sandwich_tex_ftplugin') || get(g:, 'sandwich_no_tex_ftplugin', 0)
|
||||
finish
|
||||
endif
|
||||
|
||||
runtime macros/sandwich/ftplugin/tex.vim
|
||||
|
||||
augroup sandwich-event-FileType
|
||||
autocmd!
|
||||
execute 'autocmd FileType tex source ' . fnameescape(expand('<sfile>'))
|
||||
augroup END
|
||||
|
||||
let b:did_sandwich_tex_ftplugin = 1
|
||||
if !exists('b:undo_ftplugin')
|
||||
let b:undo_ftplugin = ''
|
||||
else
|
||||
let b:undo_ftplugin .= ' | '
|
||||
endif
|
||||
let b:undo_ftplugin .= 'unlet b:did_sandwich_tex_ftplugin | call sandwich#util#ftrevert("tex")'
|
||||
@ -0,0 +1,20 @@
|
||||
if &compatible || exists('b:did_sandwich_vim_ftplugin') || get(g:, 'sandwich_no_vim_ftplugin', 0)
|
||||
finish
|
||||
endif
|
||||
|
||||
let b:sandwich_magicchar_f_patterns = [
|
||||
\ {
|
||||
\ 'header' : '\C\<\%(\h\|[sa]:\h\|g:[A-Z]\)\k*',
|
||||
\ 'bra' : '(',
|
||||
\ 'ket' : ')',
|
||||
\ 'footer' : '',
|
||||
\ },
|
||||
\ ]
|
||||
|
||||
let b:did_sandwich_vim_ftplugin = 1
|
||||
if !exists('b:undo_ftplugin')
|
||||
let b:undo_ftplugin = ''
|
||||
else
|
||||
let b:undo_ftplugin .= ' | '
|
||||
endif
|
||||
let b:undo_ftplugin .= 'unlet! b:did_sandwich_vim_ftplugin b:sandwich_magicchar_f_patterns'
|
||||
@ -0,0 +1,607 @@
|
||||
" operator-sandwich: wrap by buns!
|
||||
" TODO Give API to get information from operator object.
|
||||
" It would be helpful for users in use of 'expr_filter' and 'command' option.
|
||||
" TODO Add 'at' option
|
||||
|
||||
" variables "{{{
|
||||
let s:constants = function('sandwich#constants#get')
|
||||
|
||||
" patchs
|
||||
if v:version > 704 || (v:version == 704 && has('patch237'))
|
||||
let s:has_patch_7_4_392 = has('patch-7.4.392')
|
||||
else
|
||||
let s:has_patch_7_4_392 = v:version == 704 && has('patch392')
|
||||
endif
|
||||
|
||||
" Others
|
||||
" NOTE: This would be updated in each operator functions (operator#sandwich#{add/delete/replce})
|
||||
let s:is_in_cmdline_window = 0
|
||||
" Current set operator
|
||||
let s:operator = ''
|
||||
"}}}
|
||||
" highlight {{{
|
||||
function! s:default_highlight() abort
|
||||
highlight default link OperatorSandwichBuns IncSearch
|
||||
highlight default link OperatorSandwichAdd DiffAdd
|
||||
highlight default link OperatorSandwichDelete DiffDelete
|
||||
|
||||
if hlexists('OperatorSandwichStuff')
|
||||
highlight default link OperatorSandwichChange OperatorSandwichStuff
|
||||
else
|
||||
" obsolete
|
||||
highlight default link OperatorSandwichChange DiffChange
|
||||
endif
|
||||
endfunction
|
||||
call s:default_highlight()
|
||||
|
||||
augroup sandwich-event-ColorScheme
|
||||
autocmd!
|
||||
autocmd ColorScheme * call s:default_highlight()
|
||||
augroup END
|
||||
"}}}
|
||||
|
||||
""" Public functions
|
||||
" Prerequisite
|
||||
function! operator#sandwich#prerequisite(kind, mode, ...) abort "{{{
|
||||
" make new operator object
|
||||
let g:operator#sandwich#object = operator#sandwich#operator#new()
|
||||
|
||||
" prerequisite
|
||||
let operator = g:operator#sandwich#object
|
||||
let operator.state = 1
|
||||
let operator.kind = a:kind
|
||||
let operator.count = a:mode ==# 'x' ? max([1, v:prevcount]) : v:count1
|
||||
let operator.mode = a:mode
|
||||
let operator.view = winsaveview()
|
||||
let operator.cursor.keepable = 1
|
||||
let operator.cursor.keep[0:3] = getpos('.')[0:3]
|
||||
let operator.opt = sandwich#opt#new(a:kind, {}, get(a:000, 0, {}))
|
||||
let operator.recipes.arg = get(a:000, 1, [])
|
||||
let operator.recipes.arg_given = a:0 > 1
|
||||
|
||||
let [operator.extended, operator.blockwidth] = s:blockwisevisual_info(a:mode)
|
||||
|
||||
let &l:operatorfunc = 'operator#sandwich#' . a:kind
|
||||
let s:operator = a:kind
|
||||
return
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#keymap(kind, mode, ...) abort "{{{
|
||||
if a:0 == 0
|
||||
call operator#sandwich#prerequisite(a:kind, a:mode)
|
||||
elseif a:0 == 1
|
||||
call operator#sandwich#prerequisite(a:kind, a:mode, a:1)
|
||||
else
|
||||
call operator#sandwich#prerequisite(a:kind, a:mode, a:1, a:2)
|
||||
endif
|
||||
|
||||
let cmd = a:mode ==# 'x' ? 'gvg@' : 'g@'
|
||||
call feedkeys(cmd, 'inx')
|
||||
return
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:blockwisevisual_info(mode) abort "{{{
|
||||
if a:mode ==# 'x' && visualmode() ==# "\<C-v>"
|
||||
" The case for blockwise selections in visual mode
|
||||
" NOTE: 'extended' could be recorded safely only at here. Do not move.
|
||||
let registers = s:saveregisters()
|
||||
let lazyredraw = &lazyredraw
|
||||
set lazyredraw
|
||||
let view = winsaveview()
|
||||
try
|
||||
normal! gv
|
||||
let extended = winsaveview().curswant == s:constants('colmax')
|
||||
silent noautocmd normal! ""y
|
||||
let regtype = getregtype('"')
|
||||
finally
|
||||
call winrestview(view)
|
||||
let &lazyredraw = lazyredraw
|
||||
call s:restoreregisters(registers)
|
||||
endtry
|
||||
let blockwidth = str2nr(regtype[1:])
|
||||
else
|
||||
let extended = 0
|
||||
let blockwidth = 0
|
||||
endif
|
||||
return [extended, blockwidth]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:saveregisters() abort "{{{
|
||||
let registers = {}
|
||||
let registers['0'] = s:getregister('0')
|
||||
let registers['"'] = s:getregister('"')
|
||||
return registers
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:restoreregisters(registers) abort "{{{
|
||||
for [register, contains] in items(a:registers)
|
||||
call s:setregister(register, contains)
|
||||
endfor
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:getregister(register) abort "{{{
|
||||
return [getreg(a:register), getregtype(a:register)]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:setregister(register, contains) abort "{{{
|
||||
let [value, options] = a:contains
|
||||
return setreg(a:register, value, options)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" Operator functions
|
||||
function! operator#sandwich#add(motionwise, ...) abort "{{{
|
||||
call s:do('add', a:motionwise, 'OperatorSandwichAddPre', 'OperatorSandwichAddPost')
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#delete(motionwise, ...) abort "{{{
|
||||
call s:do('delete', a:motionwise, 'OperatorSandwichDeletePre', 'OperatorSandwichDeletePost')
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#replace(motionwise, ...) abort "{{{
|
||||
call s:do('replace', a:motionwise, 'OperatorSandwichReplacePre', 'OperatorSandwichReplacePost')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:do(kind, motionwise, AutocmdPre, AutocmdPost) abort "{{{
|
||||
let s:operator = ''
|
||||
if exists('g:operator#sandwich#object')
|
||||
let operator = g:operator#sandwich#object
|
||||
let messenger = sandwich#messenger#new()
|
||||
let defaultopt = s:default_options(a:kind, a:motionwise)
|
||||
call operator.opt.update('default', defaultopt)
|
||||
call s:update_is_in_cmdline_window()
|
||||
call s:doautocmd(a:AutocmdPre)
|
||||
call operator.execute(a:motionwise)
|
||||
call s:doautocmd(a:AutocmdPost)
|
||||
call messenger.notify('operator-sandwich: ')
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
" function! s:update_is_in_cmdline_window() abort "{{{
|
||||
if s:has_patch_7_4_392
|
||||
function! s:update_is_in_cmdline_window() abort
|
||||
let s:is_in_cmdline_window = getcmdwintype() !=# ''
|
||||
endfunction
|
||||
else
|
||||
function! s:update_is_in_cmdline_window() abort
|
||||
let s:is_in_cmdline_window = 0
|
||||
try
|
||||
execute 'tabnext ' . tabpagenr()
|
||||
catch /^Vim\%((\a\+)\)\=:E11/
|
||||
let s:is_in_cmdline_window = 1
|
||||
catch
|
||||
endtry
|
||||
endfunction
|
||||
endif
|
||||
"}}}
|
||||
function! s:doautocmd(name) abort "{{{
|
||||
let view = s:saveview()
|
||||
try
|
||||
if exists('#User#' . a:name)
|
||||
execute 'doautocmd <nomodeline> User ' . a:name
|
||||
endif
|
||||
catch
|
||||
let errormsg = printf('operator-sandwich: An error occurred in autocmd %s. [%s]', a:name, v:exception)
|
||||
echoerr errormsg
|
||||
finally
|
||||
call s:restview(view, a:name)
|
||||
endtry
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:saveview() abort "{{{
|
||||
return [tabpagenr(), winnr(), winsaveview(), getpos("'["), getpos("']")]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:restview(view, name) abort "{{{
|
||||
let [tabpagenr, winnr, view, modhead, modtail] = a:view
|
||||
|
||||
if s:is_in_cmdline_window
|
||||
" in cmdline-window
|
||||
return
|
||||
endif
|
||||
|
||||
" tabpage
|
||||
try
|
||||
execute 'tabnext ' . tabpagenr
|
||||
if tabpagenr() != tabpagenr
|
||||
throw 'OperatorSandwichError:CouldNotRestoreTabpage'
|
||||
endif
|
||||
catch /^OperatorSandwichError:CouldNotRestoreTabpage/
|
||||
let errormsg = printf('operator-sandwich: Could not have restored tabpage after autocmd %s.', a:name)
|
||||
echoerr errormsg
|
||||
endtry
|
||||
|
||||
" window
|
||||
try
|
||||
execute winnr . 'wincmd w'
|
||||
catch /^Vim\%((\a\+)\)\=:E16/
|
||||
let errormsg = printf('operator-sandwich: Could not have restored window after autocmd %s.', a:name)
|
||||
echoerr errormsg
|
||||
endtry
|
||||
" view
|
||||
call winrestview(view)
|
||||
" marks
|
||||
call setpos("'[", modhead)
|
||||
call setpos("']", modtail)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" For the query1st series mappings
|
||||
function! operator#sandwich#query1st(kind, mode, ...) abort "{{{
|
||||
if a:kind !=# 'add' && a:kind !=# 'replace'
|
||||
return
|
||||
endif
|
||||
|
||||
" prerequisite
|
||||
let arg_opt = get(a:000, 0, {})
|
||||
let arg_recipes = get(a:000, 1, [])
|
||||
call operator#sandwich#prerequisite(a:kind, a:mode, arg_opt, arg_recipes)
|
||||
let operator = g:operator#sandwich#object
|
||||
" NOTE: force to set highlight=0 and query_once=1
|
||||
call operator.opt.update('default', {'highlight': 0, 'query_once': 1, 'expr': 0, 'listexpr': 0})
|
||||
let operator.recipes.arg_given = a:0 > 1
|
||||
|
||||
let stuff = operator#sandwich#stuff#new()
|
||||
call stuff.initialize(operator.count, operator.cursor, operator.modmark)
|
||||
let operator.basket = [stuff]
|
||||
|
||||
" pick 'recipe' up and query prefered buns
|
||||
call operator.recipes.integrate(a:kind, 'all', a:mode)
|
||||
for i in range(operator.count)
|
||||
let opt = operator.opt
|
||||
let recipe = operator.query()
|
||||
let operator.recipes.dog_ear += [recipe]
|
||||
if !has_key(recipe, 'buns') || recipe.buns == []
|
||||
break
|
||||
endif
|
||||
|
||||
call opt.update('recipe_add', recipe)
|
||||
if i == 0 && operator.count > 1 && opt.of('query_once')
|
||||
call operator.recipes.fill(recipe, operator.count)
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
|
||||
if filter(copy(operator.recipes.dog_ear), 'has_key(v:val, "buns")') != []
|
||||
let operator.state = 0
|
||||
let cmd = a:mode ==# 'x'
|
||||
\ ? "\<Plug>(operator-sandwich-gv)\<Plug>(operator-sandwich-g@)"
|
||||
\ : "\<Plug>(operator-sandwich-g@)"
|
||||
call feedkeys(cmd, 'im')
|
||||
else
|
||||
unlet g:operator#sandwich#object
|
||||
endif
|
||||
return
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" Supplementary keymappings
|
||||
function! operator#sandwich#synchro_count() abort "{{{
|
||||
if exists('g:operator#sandwich#object')
|
||||
return g:operator#sandwich#object.count
|
||||
else
|
||||
return ''
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#release_count() abort "{{{
|
||||
if exists('g:operator#sandwich#object')
|
||||
let l:count = g:operator#sandwich#object.count
|
||||
let g:operator#sandwich#object.count = 1
|
||||
return l:count
|
||||
else
|
||||
return ''
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#squash_count() abort "{{{
|
||||
if exists('g:operator#sandwich#object')
|
||||
let g:operator#sandwich#object.count = 1
|
||||
endif
|
||||
return ''
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#predot() abort "{{{
|
||||
if exists('g:operator#sandwich#object')
|
||||
let operator = g:operator#sandwich#object
|
||||
let operator.cursor.keepable = 1
|
||||
let operator.cursor.keep[0:3] = getpos('.')[0:3]
|
||||
endif
|
||||
return ''
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#dot() abort "{{{
|
||||
call operator#sandwich#predot()
|
||||
return '.'
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" visualrepeat.vim (vimscript #3848) support
|
||||
function! operator#sandwich#visualrepeat(kind) abort "{{{
|
||||
let operator = g:operator#sandwich#object
|
||||
|
||||
let original_mode = operator.mode
|
||||
let original_extended = operator.extended
|
||||
let original_blockwidth = operator.blockwidth
|
||||
|
||||
let operator.mode = 'x'
|
||||
let [operator.extended, operator.blockwidth] = s:blockwisevisual_info('x')
|
||||
try
|
||||
normal! gv
|
||||
let operator.cursor.keepable = 1
|
||||
let operator.cursor.keep[0:3] = getpos('.')[0:3]
|
||||
|
||||
let l:count = v:count ? v:count : ''
|
||||
let &l:operatorfunc = 'operator#sandwich#' . a:kind
|
||||
let cmd = printf("normal! %sg@", l:count)
|
||||
execute cmd
|
||||
finally
|
||||
let operator.mode = original_mode
|
||||
let operator.extended = original_extended
|
||||
let operator.blockwidth = original_blockwidth
|
||||
endtry
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" API
|
||||
function! operator#sandwich#show(...) abort "{{{
|
||||
if !exists('g:operator#sandwich#object') || !g:operator#sandwich#object.at_work
|
||||
echoerr 'operator-sandwich: Not in an operator-sandwich operation!'
|
||||
return 1
|
||||
endif
|
||||
|
||||
let operator = g:operator#sandwich#object
|
||||
let kind = operator.kind
|
||||
let opt = operator.opt
|
||||
let place = get(a:000, 0, '')
|
||||
if kind ==# 'add'
|
||||
if place ==# ''
|
||||
let place = 'stuff'
|
||||
endif
|
||||
if place ==# 'added'
|
||||
let hi_group = s:get_ifnotempty(a:000, 1, 'OperatorSandwichAdd')
|
||||
else
|
||||
let hi_group = opt.of('highlight') >= 2
|
||||
\ ? s:get_ifnotempty(a:000, 1, 'OperatorSandwichChange')
|
||||
\ : s:get_ifnotempty(a:000, 1, 'OperatorSandwichBuns')
|
||||
endif
|
||||
elseif kind ==# 'delete'
|
||||
if place ==# ''
|
||||
let place = 'target'
|
||||
endif
|
||||
let hi_group = opt.of('highlight') >= 2
|
||||
\ ? s:get_ifnotempty(a:000, 1, 'OperatorSandwichDelete')
|
||||
\ : s:get_ifnotempty(a:000, 1, 'OperatorSandwichBuns')
|
||||
elseif kind ==# 'replace'
|
||||
if place ==# ''
|
||||
let place = 'target'
|
||||
endif
|
||||
if place ==# 'added'
|
||||
let hi_group = s:get_ifnotempty(a:000, 1, 'OperatorSandwichAdd')
|
||||
elseif place ==# 'target'
|
||||
let hi_group = opt.of('highlight') >= 2
|
||||
\ ? s:get_ifnotempty(a:000, 1, 'OperatorSandwichDelete')
|
||||
\ : s:get_ifnotempty(a:000, 1, 'OperatorSandwichBuns')
|
||||
else
|
||||
let hi_group = opt.of('highlight') >= 2
|
||||
\ ? s:get_ifnotempty(a:000, 1, 'OperatorSandwichChange')
|
||||
\ : s:get_ifnotempty(a:000, 1, 'OperatorSandwichBuns')
|
||||
endif
|
||||
else
|
||||
return 1
|
||||
endif
|
||||
let forcibly = get(a:000, 2, 0)
|
||||
return operator.show(place, hi_group, forcibly)
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#quench(...) abort "{{{
|
||||
if exists('g:operator#sandwich#object')
|
||||
let operator = g:operator#sandwich#object
|
||||
let kind = operator.kind
|
||||
let place = get(a:000, 0, '')
|
||||
if place ==# ''
|
||||
if kind ==# 'add'
|
||||
let place = 'stuff'
|
||||
elseif kind ==# 'delete'
|
||||
let place = 'target'
|
||||
elseif kind ==# 'replace'
|
||||
let place = 'target'
|
||||
else
|
||||
return 1
|
||||
endif
|
||||
endif
|
||||
return operator.quench(place)
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#get_info(...) abort "{{{
|
||||
if !exists('g:operator#sandwich#object') || !g:operator#sandwich#object.at_work
|
||||
echoerr 'operator-sandwich: Not in an operator-sandwich operation!'
|
||||
return 1
|
||||
endif
|
||||
|
||||
let info = get(a:000, 0, '')
|
||||
if a:0 == 0 || info ==# ''
|
||||
return g:operator#sandwich#object
|
||||
elseif info ==# 'state' || info ==# 'kind' || info ==# 'count' || info ==# 'mode' || info ==# 'motionwise'
|
||||
return g:operator#sandwich#object[info]
|
||||
else
|
||||
echoerr printf('operator-sandwich: Identifier "%s" is not supported.', string(info))
|
||||
return 1
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#kind() abort "{{{
|
||||
return exists('g:operator#sandwich#object') && g:operator#sandwich#object.at_work
|
||||
\ ? g:operator#sandwich#object.kind
|
||||
\ : s:operator
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_ifnotempty(list, idx, default) abort "{{{
|
||||
let item = get(a:list, a:idx, '')
|
||||
if item ==# ''
|
||||
let item = a:default
|
||||
endif
|
||||
return item
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" For internal communication
|
||||
function! operator#sandwich#is_in_cmd_window() abort "{{{
|
||||
return s:is_in_cmdline_window
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#synchronize(kind, recipe) abort "{{{
|
||||
if exists('g:operator#sandwich#object') && !empty(a:recipe)
|
||||
let g:operator#sandwich#object.recipes.synchro.on = 1
|
||||
let g:operator#sandwich#object.recipes.synchro.kind = a:kind
|
||||
let g:operator#sandwich#object.recipes.synchro.recipe = [a:recipe]
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" recipes "{{{
|
||||
function! operator#sandwich#get_recipes() abort "{{{
|
||||
if exists('b:operator_sandwich_recipes')
|
||||
let recipes = b:operator_sandwich_recipes
|
||||
elseif exists('g:operator#sandwich#recipes')
|
||||
let recipes = g:operator#sandwich#recipes
|
||||
else
|
||||
let recipes = g:operator#sandwich#default_recipes
|
||||
endif
|
||||
return deepcopy(recipes)
|
||||
endfunction
|
||||
"}}}
|
||||
if exists('g:operator#sandwich#default_recipes')
|
||||
unlockvar! g:operator#sandwich#default_recipes
|
||||
endif
|
||||
let g:operator#sandwich#default_recipes = []
|
||||
lockvar! g:operator#sandwich#default_recipes
|
||||
"}}}
|
||||
|
||||
" options "{{{
|
||||
function! s:default_options(kind, motionwise) abort "{{{
|
||||
return get(b:, 'operator_sandwich_options', g:operator#sandwich#options)[a:kind][a:motionwise]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:initialize_options(...) abort "{{{
|
||||
let manner = a:0 ? a:1 : 'keep'
|
||||
let g:operator#sandwich#options = get(g:, 'operator#sandwich#options', {})
|
||||
for kind in ['add', 'delete', 'replace']
|
||||
if !has_key(g:operator#sandwich#options, kind)
|
||||
let g:operator#sandwich#options[kind] = {}
|
||||
endif
|
||||
for motionwise in ['char', 'line', 'block']
|
||||
if !has_key(g:operator#sandwich#options[kind], motionwise)
|
||||
let g:operator#sandwich#options[kind][motionwise] = {}
|
||||
endif
|
||||
call extend(g:operator#sandwich#options[kind][motionwise],
|
||||
\ sandwich#opt#defaults(kind, motionwise), manner)
|
||||
endfor
|
||||
endfor
|
||||
endfunction
|
||||
call s:initialize_options()
|
||||
"}}}
|
||||
function! operator#sandwich#set_default() abort "{{{
|
||||
call s:initialize_options('force')
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#set(kind, motionwise, option, value) abort "{{{
|
||||
if s:argument_error(a:kind, a:motionwise, a:option, a:value)
|
||||
return
|
||||
endif
|
||||
|
||||
if a:kind ==# 'all'
|
||||
let kinds = ['add', 'delete', 'replace']
|
||||
else
|
||||
let kinds = [a:kind]
|
||||
endif
|
||||
|
||||
if a:motionwise ==# 'all'
|
||||
let motionwises = ['char', 'line', 'block']
|
||||
else
|
||||
let motionwises = [a:motionwise]
|
||||
endif
|
||||
|
||||
call s:set_option_value(g:operator#sandwich#options, kinds, motionwises, a:option, a:value)
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#setlocal(kind, motionwise, option, value) abort "{{{
|
||||
if s:argument_error(a:kind, a:motionwise, a:option, a:value)
|
||||
return
|
||||
endif
|
||||
|
||||
if !exists('b:operator_sandwich_options')
|
||||
let b:operator_sandwich_options = deepcopy(g:operator#sandwich#options)
|
||||
endif
|
||||
|
||||
if a:kind ==# 'all'
|
||||
let kinds = ['add', 'delete', 'replace']
|
||||
else
|
||||
let kinds = [a:kind]
|
||||
endif
|
||||
|
||||
if a:motionwise ==# 'all'
|
||||
let motionwises = ['char', 'line', 'block']
|
||||
else
|
||||
let motionwises = [a:motionwise]
|
||||
endif
|
||||
|
||||
call s:set_option_value(b:operator_sandwich_options, kinds, motionwises, a:option, a:value)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:argument_error(kind, motionwise, option, value) abort "{{{
|
||||
if !(a:kind ==# 'add' || a:kind ==# 'delete' || a:kind ==# 'replace' || a:kind ==# 'all')
|
||||
echohl WarningMsg
|
||||
echomsg 'Invalid kind "' . a:kind . '".'
|
||||
echohl NONE
|
||||
return 1
|
||||
endif
|
||||
|
||||
if !(a:motionwise ==# 'char' || a:motionwise ==# 'line' || a:motionwise ==# 'block' || a:motionwise ==# 'all')
|
||||
echohl WarningMsg
|
||||
echomsg 'Invalid motion-wise "' . a:motionwise . '".'
|
||||
echohl NONE
|
||||
return 1
|
||||
endif
|
||||
|
||||
if a:kind !=# 'all' && a:motionwise !=# 'all'
|
||||
let defaults = sandwich#opt#defaults(a:kind, a:motionwise)
|
||||
if filter(keys(defaults), 'v:val ==# a:option') == []
|
||||
echohl WarningMsg
|
||||
echomsg 'Invalid option name "' . a:option . '".'
|
||||
echohl NONE
|
||||
return 1
|
||||
endif
|
||||
|
||||
if a:option !~# 'indentkeys[-+]\?' && type(a:value) != type(defaults[a:option])
|
||||
echohl WarningMsg
|
||||
echomsg 'Invalid type of value. ' . string(a:value)
|
||||
echohl NONE
|
||||
return 1
|
||||
endif
|
||||
endif
|
||||
return 0
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:set_option_value(dest, kinds, motionwises, option, value) abort "{{{
|
||||
for kind in a:kinds
|
||||
for motionwise in a:motionwises
|
||||
let defaults = sandwich#opt#defaults(kind, motionwise)
|
||||
if filter(keys(defaults), 'v:val ==# a:option') != []
|
||||
if a:option =~# 'indentkeys[-+]\?' || type(a:value) == type(defaults[a:option])
|
||||
let a:dest[kind][motionwise][a:option] = a:value
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
endfor
|
||||
endfunction
|
||||
"}}}
|
||||
"}}}
|
||||
|
||||
unlet! g:operator#sandwich#object
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,836 @@
|
||||
" act object - editing buffer
|
||||
|
||||
let s:lib = operator#sandwich#lib#import()
|
||||
|
||||
" variables "{{{
|
||||
let s:constants = function('sandwich#constants#get')
|
||||
let s:TRUE = 1
|
||||
let s:FALSE = 0
|
||||
function! s:SID() abort
|
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$')
|
||||
endfunction
|
||||
let s:SNR = printf("\<SNR>%s_", s:SID())
|
||||
delfunction s:SID
|
||||
|
||||
nnoremap <SID>(i) i
|
||||
nnoremap <SID>(o) o
|
||||
nnoremap <SID>(O) O
|
||||
|
||||
let s:KEY_i = printf('%s(i)', s:SNR)
|
||||
let s:KEY_o = printf('%s(o)', s:SNR)
|
||||
let s:KEY_O = printf('%s(O)', s:SNR)
|
||||
|
||||
" null valiables
|
||||
let s:null_pos = [0, 0, 0, 0]
|
||||
let s:null_4pos = {
|
||||
\ 'head1': copy(s:null_pos),
|
||||
\ 'tail1': copy(s:null_pos),
|
||||
\ 'head2': copy(s:null_pos),
|
||||
\ 'tail2': copy(s:null_pos),
|
||||
\ }
|
||||
|
||||
" types
|
||||
let s:type_str = type('')
|
||||
|
||||
" features
|
||||
let s:has_gui_running = has('gui_running')
|
||||
"}}}
|
||||
|
||||
function! operator#sandwich#act#new() abort "{{{
|
||||
return deepcopy(s:act)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" s:act "{{{
|
||||
let s:act = {
|
||||
\ 'cursor' : {},
|
||||
\ 'modmark': {},
|
||||
\ 'opt' : {},
|
||||
\ 'success': 0,
|
||||
\ 'added' : [],
|
||||
\ }
|
||||
"}}}
|
||||
function! s:act.initialize(cursor, modmark, added) dict abort "{{{
|
||||
let self.cursor = a:cursor
|
||||
let self.modmark = a:modmark
|
||||
let self.opt = {}
|
||||
let self.added = a:added
|
||||
let self.success = 0
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:act.add_pair(buns, stuff, undojoin) dict abort "{{{
|
||||
let target = a:stuff.target
|
||||
let edges = a:stuff.edges
|
||||
let modmark = self.modmark
|
||||
let opt = self.opt
|
||||
let indent = [0, 0]
|
||||
let is_linewise = [0, 0]
|
||||
|
||||
if s:lib.is_valid_4pos(target) && s:lib.is_equal_or_ahead(target.head2, target.head1)
|
||||
if target.head2[2] != col([target.head2[1], '$'])
|
||||
let target.head2[0:3] = s:lib.get_right_pos(target.head2)
|
||||
endif
|
||||
|
||||
let indentopt = s:set_indent(opt)
|
||||
let messenger = sandwich#messenger#get()
|
||||
try
|
||||
let pos = target.head1
|
||||
let [is_linewise[0], indent[0], head1, tail1] = s:add_former(a:buns, pos, opt, a:undojoin)
|
||||
let pos = s:push1(copy(target.head2), target, a:buns, indent, is_linewise)
|
||||
let [is_linewise[1], indent[1], head2, tail2] = s:add_latter(a:buns, pos, opt)
|
||||
catch /^Vim\%((\a\+)\)\=:E21/
|
||||
call messenger.notice.queue(['Cannot make changes to read-only buffer.', 'WarningMsg'])
|
||||
throw 'OperatorSandwichError:ReadOnly'
|
||||
finally
|
||||
call s:restore_indent(indentopt)
|
||||
endtry
|
||||
let [mod_head, mod_tail] = s:execute_command(head1, tail2, opt.of('command'))
|
||||
|
||||
if opt.of('highlight', '') >= 3
|
||||
call map(self.added, 's:shift_added("s:shift_for_add", v:val, target, a:buns, indent, is_linewise)')
|
||||
call add(self.added, {
|
||||
\ 'head1': head1,
|
||||
\ 'tail1': s:added_tail(head1, tail1, is_linewise[0]),
|
||||
\ 'head2': head2,
|
||||
\ 'tail2': s:added_tail(head2, tail2, is_linewise[1]),
|
||||
\ 'linewise': is_linewise,
|
||||
\ })
|
||||
endif
|
||||
|
||||
" update modmark
|
||||
if modmark.head == s:null_pos || s:lib.is_ahead(modmark.head, mod_head)
|
||||
let modmark.head = mod_head
|
||||
endif
|
||||
if modmark.tail == s:null_pos
|
||||
let modmark.tail = mod_tail
|
||||
else
|
||||
call s:shift_for_add(modmark.tail, target, a:buns, indent, is_linewise)
|
||||
if s:lib.is_ahead(mod_tail, modmark.tail)
|
||||
let modmark.tail = mod_tail
|
||||
endif
|
||||
endif
|
||||
|
||||
" update cursor positions
|
||||
call s:shift_for_add(self.cursor.inner_head, target, a:buns, indent, is_linewise)
|
||||
call s:shift_for_add(self.cursor.keep, target, a:buns, indent, is_linewise)
|
||||
call s:shift_for_add(self.cursor.inner_tail, target, a:buns, indent, is_linewise)
|
||||
|
||||
" update next target positions
|
||||
let edges.head = copy(head1)
|
||||
let edges.tail = s:lib.get_left_pos(tail2)
|
||||
|
||||
let self.success = 1
|
||||
endif
|
||||
return self.success
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:act.delete_pair(stuff, modified) dict abort "{{{
|
||||
let target = a:stuff.target
|
||||
let edges = a:stuff.edges
|
||||
let modmark = self.modmark
|
||||
let opt = self.opt
|
||||
|
||||
if s:lib.is_valid_4pos(target) && s:lib.is_ahead(target.head2, target.tail1)
|
||||
let reg = ['"', getreg('"'), getregtype('"')]
|
||||
let deletion = ['', '']
|
||||
let is_linewise = [0, 0]
|
||||
let messenger = sandwich#messenger#get()
|
||||
try
|
||||
let former_head = target.head1
|
||||
let former_tail = target.tail1
|
||||
let latter_head = target.head2
|
||||
let [deletion[0], is_linewise[0], head] = s:delete_former(former_head, former_tail, latter_head, opt)
|
||||
|
||||
let latter_head = s:pull1(copy(target.head2), target, deletion, is_linewise)
|
||||
let latter_tail = s:pull1(copy(target.tail2), target, deletion, is_linewise)
|
||||
let [deletion[1], is_linewise[1], tail] = s:delete_latter(latter_head, latter_tail, former_head, opt)
|
||||
catch /^Vim\%((\a\+)\)\=:E21/
|
||||
call messenger.notice.queue(['Cannot make changes to read-only buffer.', 'WarningMsg'])
|
||||
throw 'OperatorSandwichError:ReadOnly'
|
||||
finally
|
||||
call call('setreg', reg)
|
||||
endtry
|
||||
let [mod_head, mod_tail] = s:execute_command(head, tail, opt.of('command'))
|
||||
|
||||
" update modmark
|
||||
if modmark.head == s:null_pos || s:lib.is_ahead(modmark.head, mod_head)
|
||||
let modmark.head = mod_head
|
||||
endif
|
||||
" NOTE: Probably, there is no possibility to delete breakings along multiple acts.
|
||||
if !a:modified
|
||||
if modmark.tail == s:null_pos
|
||||
let modmark.tail = mod_tail
|
||||
else
|
||||
call s:shift_for_delete(modmark.tail, target, deletion, is_linewise)
|
||||
if mod_tail[1] >= modmark.tail[1]
|
||||
let modmark.tail = mod_tail
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
" update cursor positions
|
||||
call s:shift_for_delete(self.cursor.inner_head, target, deletion, is_linewise)
|
||||
call s:shift_for_delete(self.cursor.keep, target, deletion, is_linewise)
|
||||
call s:shift_for_delete(self.cursor.inner_tail, target, deletion, is_linewise)
|
||||
|
||||
" update target positions
|
||||
let edges.head = copy(head)
|
||||
let edges.tail = s:lib.get_left_pos(tail)
|
||||
|
||||
let self.success = 1
|
||||
endif
|
||||
return self.success
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:act.replace_pair(buns, stuff, undojoin, modified) dict abort "{{{
|
||||
let target = a:stuff.target
|
||||
let edges = a:stuff.edges
|
||||
let modmark = self.modmark
|
||||
let opt = self.opt
|
||||
|
||||
if s:lib.is_valid_4pos(target) && s:lib.is_ahead(target.head2, target.tail1)
|
||||
set virtualedit=
|
||||
let next_head = s:lib.get_right_pos(target.tail1)
|
||||
let next_tail = s:lib.get_left_pos(target.head2)
|
||||
set virtualedit=onemore
|
||||
|
||||
let reg = ['"', getreg('"'), getregtype('"')]
|
||||
let deletion = ['', '']
|
||||
let indent = [0, 0]
|
||||
let is_linewise = [0, 0]
|
||||
let indentopt = s:set_indent(opt)
|
||||
let messenger = sandwich#messenger#get()
|
||||
try
|
||||
let within_a_line = target.tail1[1] == target.head2[1]
|
||||
let former_head = target.head1
|
||||
let former_tail = target.tail1
|
||||
let latter_head = copy(target.head2)
|
||||
let latter_tail = copy(target.tail2)
|
||||
let [deletion[0], is_linewise[0], indent[0], head1, tail1] = s:replace_former(a:buns, former_head, former_tail, within_a_line, opt, a:undojoin)
|
||||
|
||||
call s:pull1(latter_head, target, deletion, is_linewise)
|
||||
call s:push1(latter_head, target, a:buns, indent, is_linewise)
|
||||
call s:pull1(latter_tail, target, deletion, is_linewise)
|
||||
call s:push1(latter_tail, target, a:buns, indent, is_linewise)
|
||||
let [deletion[1], is_linewise[1], indent[1], head2, tail2] = s:replace_latter(a:buns, latter_head, latter_tail, within_a_line, opt)
|
||||
catch /^Vim\%((\a\+)\)\=:E21/
|
||||
call messenger.notice.queue(['Cannot make changes to read-only buffer.', 'WarningMsg'])
|
||||
throw 'OperatorSandwichError:ReadOnly'
|
||||
finally
|
||||
call call('setreg', reg)
|
||||
call s:restore_indent(indentopt)
|
||||
endtry
|
||||
let [mod_head, mod_tail] = s:execute_command(head1, tail2, opt.of('command'))
|
||||
|
||||
if opt.of('highlight', '') >= 3
|
||||
call map(self.added, 's:shift_added("s:shift_for_replace", v:val, target, a:buns, deletion, indent, is_linewise)')
|
||||
call add(self.added, {
|
||||
\ 'head1': head1,
|
||||
\ 'tail1': s:lib.get_left_pos(tail1),
|
||||
\ 'head2': head2,
|
||||
\ 'tail2': s:lib.get_left_pos(tail2),
|
||||
\ 'linewise': is_linewise
|
||||
\ })
|
||||
endif
|
||||
|
||||
" update modmark
|
||||
if modmark.head == s:null_pos || s:lib.is_ahead(modmark.head, mod_head)
|
||||
let modmark.head = copy(mod_head)
|
||||
endif
|
||||
if !a:modified
|
||||
if modmark.tail == s:null_pos
|
||||
let modmark.tail = copy(mod_tail)
|
||||
else
|
||||
call s:shift_for_replace(modmark.tail, target, a:buns, deletion, indent, is_linewise)
|
||||
if modmark.tail[1] < mod_tail[1]
|
||||
let modmark.tail = copy(mod_tail)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
" update cursor positions
|
||||
call s:shift_for_replace(self.cursor.keep, target, a:buns, deletion, indent, is_linewise)
|
||||
call s:shift_for_replace(next_head, target, a:buns, deletion, indent, is_linewise)
|
||||
call s:shift_for_replace(next_tail, target, a:buns, deletion, indent, is_linewise)
|
||||
if self.cursor.inner_head == s:null_pos || target.head1[1] <= self.cursor.inner_head[1]
|
||||
let self.cursor.inner_head = copy(next_head)
|
||||
endif
|
||||
if self.cursor.inner_tail == s:null_pos
|
||||
let self.cursor.inner_tail = copy(next_tail)
|
||||
else
|
||||
call s:shift_for_replace(self.cursor.inner_tail, target, a:buns, deletion, indent, is_linewise)
|
||||
if self.cursor.inner_tail[1] <= next_tail[1]
|
||||
let self.cursor.inner_tail = copy(next_tail)
|
||||
endif
|
||||
endif
|
||||
|
||||
" update target positions
|
||||
let edges.head = next_head
|
||||
let edges.tail = next_tail
|
||||
|
||||
let self.success = 1
|
||||
endif
|
||||
return self.success
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" supplemental classes
|
||||
" Indent class {{{
|
||||
let s:Indent = {
|
||||
\ 'len': 0,
|
||||
\ 'str': '',
|
||||
\ 'savedstr': '',
|
||||
\ 'linehead': s:FALSE
|
||||
\ }
|
||||
function! s:Indent(pos, opt) abort "{{{
|
||||
let line = getline(a:pos[1])
|
||||
let indent = deepcopy(s:Indent)
|
||||
if a:pos[2] == 1
|
||||
let indent.linehead = s:TRUE
|
||||
return indent
|
||||
endif
|
||||
|
||||
let indent.str = matchstr(line, '^\s*')
|
||||
let indent.len = strlen(indent.str)
|
||||
if a:pos[2] <= indent.len
|
||||
" shorten if cursor is in indentation
|
||||
let indent.str = indent.str[: a:pos[2] - 2]
|
||||
let indent.len = strlen(indent.str)
|
||||
let indent.linehead = s:TRUE
|
||||
endif
|
||||
|
||||
if a:opt.of('linewise') && a:opt.of('autoindent') == 4
|
||||
let indent.savedstr = indent.str
|
||||
endif
|
||||
return indent
|
||||
endfunction "}}}
|
||||
function! s:Indent.diff(addition) abort "{{{
|
||||
let addition = split(a:addition, '\m\%(\n\|\r\|\r\n\)', 1)
|
||||
if len(addition) == 1
|
||||
let indentstr = matchstr(getline("'["), '\m^\s*')
|
||||
let indentlen = strlen(indentstr)
|
||||
if self.linehead
|
||||
let indentlen -= strlen(matchstr(addition[0], '\m^\s*'))
|
||||
endif
|
||||
let diff = indentlen - self.len
|
||||
else
|
||||
let indentstr = matchstr(getline("']"), '\m^\s*')
|
||||
let indentlen = strlen(indentstr)
|
||||
let diff = indentlen - strlen(matchstr(addition[-1], '\m^\s*'))
|
||||
endif
|
||||
return diff
|
||||
endfunction "}}}
|
||||
"}}}
|
||||
|
||||
" private functions
|
||||
function! s:set_indent(opt) abort "{{{
|
||||
let indentopt = {
|
||||
\ 'autoindent': {
|
||||
\ 'restore': 0,
|
||||
\ 'value' : [&l:autoindent, &l:smartindent, &l:cindent, &l:indentexpr],
|
||||
\ },
|
||||
\ 'indentkeys': {
|
||||
\ 'restore': 0,
|
||||
\ 'name' : '',
|
||||
\ 'value' : '',
|
||||
\ },
|
||||
\ }
|
||||
|
||||
" set autoindent options
|
||||
if a:opt.of('autoindent') == 0 || a:opt.of('autoindent') == 4
|
||||
let [&l:autoindent, &l:smartindent, &l:cindent, &l:indentexpr] = [0, 0, 0, '']
|
||||
let indentopt.autoindent.restore = 1
|
||||
elseif a:opt.of('autoindent') == 1
|
||||
let [&l:autoindent, &l:smartindent, &l:cindent, &l:indentexpr] = [1, 0, 0, '']
|
||||
let indentopt.autoindent.restore = 1
|
||||
elseif a:opt.of('autoindent') == 2
|
||||
" NOTE: 'Smartindent' requires 'autoindent'. :help 'smartindent'
|
||||
let [&l:autoindent, &l:smartindent, &l:cindent, &l:indentexpr] = [1, 1, 0, '']
|
||||
let indentopt.autoindent.restore = 1
|
||||
elseif a:opt.of('autoindent') == 3
|
||||
let [&l:cindent, &l:indentexpr] = [1, '']
|
||||
let indentopt.autoindent.restore = 1
|
||||
endif
|
||||
|
||||
" set indentkeys
|
||||
if &l:indentexpr !=# ''
|
||||
let indentopt.indentkeys.name = 'indentkeys'
|
||||
let indentopt.indentkeys.value = &l:indentkeys
|
||||
else
|
||||
let indentopt.indentkeys.name = 'cinkeys'
|
||||
let indentopt.indentkeys.value = &l:cinkeys
|
||||
endif
|
||||
|
||||
let val = a:opt.of('indentkeys')
|
||||
if type(val) == s:type_str
|
||||
execute printf('setlocal %s=%s', indentopt.indentkeys.name, val)
|
||||
let indentopt.indentkeys.restore = 1
|
||||
endif
|
||||
|
||||
let val = a:opt.of('indentkeys+')
|
||||
if type(val) == s:type_str && val !=# ''
|
||||
execute printf('setlocal %s+=%s', indentopt.indentkeys.name, val)
|
||||
let indentopt.indentkeys.restore = 1
|
||||
endif
|
||||
|
||||
let val = a:opt.of('indentkeys-')
|
||||
if type(val) == s:type_str && val !=# ''
|
||||
" It looks there is no way to add ',' itself to 'indentkeys'
|
||||
for item in split(val, ',')
|
||||
execute printf('setlocal %s-=%s', indentopt.indentkeys.name, item)
|
||||
endfor
|
||||
let indentopt.indentkeys.restore = 1
|
||||
endif
|
||||
return indentopt
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:restore_indent(indentopt) abort "{{{
|
||||
" restore indentkeys first
|
||||
if a:indentopt.indentkeys.restore
|
||||
execute printf('setlocal %s=%s', a:indentopt.indentkeys.name, escape(a:indentopt.indentkeys.value, ' \'))
|
||||
endif
|
||||
|
||||
" restore autoindent options
|
||||
if a:indentopt.autoindent.restore
|
||||
let [&l:autoindent, &l:smartindent, &l:cindent, &l:indentexpr] = a:indentopt.autoindent.value
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:add_former(buns, pos, opt, ...) abort "{{{
|
||||
let undojoin_cmd = get(a:000, 0, 0) ? 'undojoin | ' : ''
|
||||
let indent = s:Indent(a:pos, a:opt)
|
||||
let opt_linewise = a:opt.of('linewise')
|
||||
if opt_linewise
|
||||
let startinsert = a:opt.of('noremap') ? 'normal! O' : 'normal ' . s:KEY_O
|
||||
let insertion = indent.savedstr . a:buns[0]
|
||||
else
|
||||
let startinsert = a:opt.of('noremap') ? 'normal! i' : 'normal ' . s:KEY_i
|
||||
let insertion = a:buns[0]
|
||||
endif
|
||||
call s:add_portion(insertion, a:pos, undojoin_cmd, startinsert)
|
||||
return [opt_linewise, indent.diff(a:buns[0]), getpos("'["), getpos("']")]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:add_latter(buns, pos, opt) abort "{{{
|
||||
let undojoin_cmd = ''
|
||||
let indent = s:Indent(a:pos, a:opt)
|
||||
let opt_linewise = a:opt.of('linewise')
|
||||
if opt_linewise
|
||||
let startinsert = a:opt.of('noremap') ? 'normal! o' : 'normal ' . s:KEY_o
|
||||
let insertion = indent.savedstr . a:buns[1]
|
||||
else
|
||||
let startinsert = a:opt.of('noremap') ? 'normal! i' : 'normal ' . s:KEY_i
|
||||
let insertion = a:buns[1]
|
||||
endif
|
||||
call s:add_portion(insertion, a:pos, undojoin_cmd, startinsert)
|
||||
return [opt_linewise, indent.diff(a:buns[1]), getpos("'["), getpos("']")]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:add_portion(bun, pos, undojoin_cmd, startinsert) abort "{{{
|
||||
call setpos('.', a:pos)
|
||||
if operator#sandwich#is_in_cmd_window()
|
||||
" workaround for a bug in cmdline-window
|
||||
call s:paste(a:bun, a:undojoin_cmd)
|
||||
else
|
||||
execute a:undojoin_cmd . 'silent noautocmd ' . a:startinsert . a:bun
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:delete_former(head, tail, latter_head, opt, ...) abort "{{{
|
||||
let is_linewise = 0
|
||||
let opt_linewise = a:opt.of('linewise')
|
||||
let undojoin_cmd = get(a:000, 0, 0) ? 'undojoin | ' : ''
|
||||
let deletion = s:delete_portion(a:head, a:tail, undojoin_cmd)
|
||||
if opt_linewise == 2 || (opt_linewise == 1 && match(getline('.'), '^\s*$') > -1)
|
||||
if line('.') != a:latter_head[1]
|
||||
.delete
|
||||
let is_linewise = 1
|
||||
endif
|
||||
let head = getpos("']")
|
||||
else
|
||||
let head = getpos('.')
|
||||
endif
|
||||
return [deletion, is_linewise, head]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:delete_latter(head, tail, former_head, opt) abort "{{{
|
||||
let is_linewise = 0
|
||||
let opt_linewise = a:opt.of('linewise')
|
||||
let undojoin_cmd = ''
|
||||
let deletion = s:delete_portion(a:head, a:tail, undojoin_cmd)
|
||||
if opt_linewise == 2 || (opt_linewise == 1 && match(getline('.'), '^\s*$') > -1)
|
||||
.delete
|
||||
let is_linewise = 1
|
||||
let tail = getpos("']")
|
||||
if tail[1] != 1 && tail[1] != a:former_head[1]
|
||||
let prevline = line("']") - 1
|
||||
let tail = [0, prevline, col([prevline, '$']), 0]
|
||||
endif
|
||||
else
|
||||
let tail = getpos("']")
|
||||
endif
|
||||
return [deletion, is_linewise, tail]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:delete_portion(head, tail, undojoin_cmd) abort "{{{
|
||||
let cmd = "%ssilent noautocmd normal! \"\"dv:call setpos('\.', %s)\<CR>"
|
||||
call setpos('.', a:head)
|
||||
let @@ = ''
|
||||
execute printf(cmd, a:undojoin_cmd, 'a:tail')
|
||||
return @@
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:replace_former(buns, head, tail, within_a_line, opt, ...) abort "{{{
|
||||
let is_linewise = 0
|
||||
let opt_linewise = a:opt.of('linewise')
|
||||
let undojoin_cmd = get(a:000, 0, 0) ? 'undojoin | ' : ''
|
||||
let deletion = s:delete_portion(a:head, a:tail, undojoin_cmd)
|
||||
let indent = s:Indent(a:head, a:opt)
|
||||
|
||||
if operator#sandwich#is_in_cmd_window()
|
||||
" workaround for a bug in cmdline-window
|
||||
call s:paste(a:buns[0])
|
||||
else
|
||||
if opt_linewise == 1 && getline('.') =~# '^\s*$'
|
||||
.delete
|
||||
let startinsert = a:opt.of('noremap', 'recipe_add') ? 'normal! O' : 'normal ' . s:KEY_O
|
||||
execute 'silent noautocmd ' . startinsert . indent.savedstr . a:buns[0]
|
||||
let is_linewise = 1
|
||||
elseif opt_linewise == 2
|
||||
if !a:within_a_line
|
||||
.delete
|
||||
endif
|
||||
let startinsert = a:opt.of('noremap', 'recipe_add') ? 'normal! O' : 'normal ' . s:KEY_O
|
||||
execute 'silent noautocmd ' . startinsert . indent.savedstr . a:buns[0]
|
||||
let is_linewise = 1
|
||||
else
|
||||
let startinsert = a:opt.of('noremap', 'recipe_add') ? 'normal! i' : 'normal ' . s:KEY_i
|
||||
execute 'silent noautocmd ' . startinsert . a:buns[0]
|
||||
endif
|
||||
endif
|
||||
return [deletion, is_linewise, indent.diff(a:buns[0]), getpos("'["), getpos("']")]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:replace_latter(buns, head, tail, within_a_line, opt) abort "{{{
|
||||
let is_linewise = 0
|
||||
let opt_linewise = a:opt.of('linewise')
|
||||
let undojoin_cmd = ''
|
||||
let deletion = s:delete_portion(a:head, a:tail, undojoin_cmd)
|
||||
let indent = s:Indent(a:head, a:opt)
|
||||
|
||||
if operator#sandwich#is_in_cmd_window()
|
||||
" workaround for a bug in cmdline-window
|
||||
call s:paste(a:buns[1])
|
||||
let head = getpos("'[")
|
||||
let tail = getpos("']")
|
||||
else
|
||||
if opt_linewise == 1 && getline('.') =~# '^\s*$'
|
||||
let startinsert = a:opt.of('noremap', 'recipe_add') ? 'normal! o' : 'normal ' . s:KEY_o
|
||||
let current = line('.')
|
||||
let fileend = line('$')
|
||||
.delete
|
||||
if current != fileend
|
||||
normal! k
|
||||
endif
|
||||
execute 'silent noautocmd ' . startinsert . indent.savedstr . a:buns[1]
|
||||
let head = getpos("'[")
|
||||
let tail = getpos("']")
|
||||
let is_linewise = 1
|
||||
elseif opt_linewise == 2
|
||||
let startinsert = a:opt.of('noremap', 'recipe_add') ? 'normal! o' : 'normal ' . s:KEY_o
|
||||
if a:within_a_line
|
||||
" exceptional behavior
|
||||
let lnum = line('.')
|
||||
execute 'silent noautocmd ' . startinsert . indent.savedstr . a:buns[1]
|
||||
let head = getpos("'[")
|
||||
let tail = getpos("']")
|
||||
execute lnum . 'delete'
|
||||
let head = [0, head[1]-1, head[2], 0]
|
||||
let tail = [0, tail[1]-1, tail[2], 0]
|
||||
else
|
||||
" usual way (same as opt_linewise == 1)
|
||||
let current = line('.')
|
||||
let fileend = line('$')
|
||||
.delete
|
||||
if current != fileend
|
||||
normal! k
|
||||
endif
|
||||
execute 'silent noautocmd ' . startinsert . indent.savedstr . a:buns[1]
|
||||
let head = getpos("'[")
|
||||
let tail = getpos("']")
|
||||
endif
|
||||
let is_linewise = 1
|
||||
else
|
||||
let startinsert = a:opt.of('noremap', 'recipe_add') ? 'normal! i' : 'normal ' . s:KEY_i
|
||||
execute 'silent noautocmd ' . startinsert . a:buns[1]
|
||||
let head = getpos("'[")
|
||||
let tail = getpos("']")
|
||||
endif
|
||||
endif
|
||||
return [deletion, is_linewise, indent.diff(a:buns[1]), head, tail]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:paste(bun, ...) abort "{{{
|
||||
let undojoin_cmd = a:0 > 0 ? a:1 : ''
|
||||
let reg = ['"', getreg('"'), getregtype('"')]
|
||||
let @@ = a:bun
|
||||
if s:has_gui_running
|
||||
execute undojoin_cmd . 'normal! ""P'
|
||||
else
|
||||
let paste = &paste
|
||||
let &paste = 1
|
||||
execute undojoin_cmd . 'normal! ""P'
|
||||
let &paste = paste
|
||||
endif
|
||||
call call('setreg', reg)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:execute_command(head, tail, command_list) abort "{{{
|
||||
let mod_head = deepcopy(a:head)
|
||||
let mod_tail = deepcopy(a:tail)
|
||||
|
||||
if a:command_list != []
|
||||
let before_mod_head = getpos("'[")
|
||||
let before_mod_tail = getpos("']")
|
||||
call setpos("'[", a:head)
|
||||
call setpos("']", a:tail)
|
||||
for command in a:command_list
|
||||
execute command
|
||||
endfor
|
||||
|
||||
let after_mod_head = getpos("'[")
|
||||
let after_mod_tail = getpos("']")
|
||||
if before_mod_head != after_mod_head || before_mod_tail != after_mod_tail
|
||||
let mod_head = after_mod_head
|
||||
let mod_tail = after_mod_tail
|
||||
endif
|
||||
endif
|
||||
return [mod_head, mod_tail]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:shift_for_add(shifted_pos, target, addition, indent, is_linewise) abort "{{{
|
||||
call s:push2(a:shifted_pos, a:target, a:addition, a:indent, a:is_linewise)
|
||||
call s:push1(a:shifted_pos, a:target, a:addition, a:indent, a:is_linewise)
|
||||
return a:shifted_pos
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:shift_for_delete(shifted_pos, target, deletion, is_linewise) abort "{{{
|
||||
call s:pull2(a:shifted_pos, a:target, a:deletion, a:is_linewise)
|
||||
call s:pull1(a:shifted_pos, a:target, a:deletion, a:is_linewise)
|
||||
return a:shifted_pos
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:shift_for_replace(shifted_pos, target, addition, deletion, indent, is_linewise) abort "{{{
|
||||
if s:lib.is_in_between(a:shifted_pos, a:target.head1, a:target.tail1)
|
||||
let startpos = copy(a:target.head1)
|
||||
let endpos = copy(startpos)
|
||||
call s:push1(endpos, a:target, a:addition, a:indent, a:is_linewise)
|
||||
let endpos = s:lib.get_left_pos(endpos)
|
||||
|
||||
if s:lib.is_equal_or_ahead(a:shifted_pos, endpos)
|
||||
let a:shifted_pos[0:3] = endpos
|
||||
endif
|
||||
elseif s:lib.is_in_between(a:shifted_pos, a:target.head2, a:target.tail2)
|
||||
let startpos = copy(a:target.head2)
|
||||
call s:pull1(startpos, a:target, a:deletion, a:is_linewise)
|
||||
call s:push1(startpos, a:target, a:addition, a:indent, a:is_linewise)
|
||||
let endpos = copy(startpos)
|
||||
let target = copy(s:null_4pos)
|
||||
let target.head2 = copy(startpos)
|
||||
call s:push2(endpos, target, a:addition, a:indent, a:is_linewise)
|
||||
let endpos = s:lib.get_left_pos(endpos)
|
||||
|
||||
call s:pull1(a:shifted_pos, a:target, a:deletion, a:is_linewise)
|
||||
call s:push1(a:shifted_pos, a:target, a:addition, a:indent, a:is_linewise)
|
||||
|
||||
if s:lib.is_equal_or_ahead(a:shifted_pos, endpos)
|
||||
let a:shifted_pos[0:3] = endpos
|
||||
endif
|
||||
else
|
||||
call s:pull2(a:shifted_pos, a:target, a:deletion, a:is_linewise)
|
||||
if a:is_linewise[1]
|
||||
let a:target.head2[1] -= 1
|
||||
endif
|
||||
call s:push2(a:shifted_pos, a:target, a:addition, a:indent, a:is_linewise)
|
||||
if a:is_linewise[1]
|
||||
let a:target.head2[1] += 1
|
||||
endif
|
||||
call s:pull1(a:shifted_pos, a:target, a:deletion, a:is_linewise)
|
||||
if a:is_linewise[0]
|
||||
let a:target.head1[1] -= 1
|
||||
endif
|
||||
call s:push1(a:shifted_pos, a:target, a:addition, a:indent, a:is_linewise)
|
||||
if a:is_linewise[0]
|
||||
let a:target.head1[1] += 1
|
||||
endif
|
||||
endif
|
||||
return a:shifted_pos
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:shift_added(func_name, added, ...) abort "{{{
|
||||
for pos in ['head1', 'tail1', 'head2', 'tail2']
|
||||
call call(a:func_name, [a:added[pos]] + a:000)
|
||||
endfor
|
||||
return a:added
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:push1(shifted_pos, target, addition, indent, is_linewise) abort "{{{
|
||||
if a:shifted_pos != s:null_pos
|
||||
let shift = [0, 0, 0, 0]
|
||||
let head = a:target.head1
|
||||
|
||||
if a:is_linewise[0] && a:shifted_pos[1] >= head[1]
|
||||
" lnum
|
||||
let shift[1] += 1
|
||||
endif
|
||||
|
||||
if s:lib.is_equal_or_ahead(a:shifted_pos, head) || (a:is_linewise[0] && a:shifted_pos[1] == head[1])
|
||||
call s:push(shift, a:shifted_pos, head, a:addition[0], a:indent[0], a:is_linewise[0])
|
||||
endif
|
||||
let a:shifted_pos[1:2] += shift[1:2]
|
||||
endif
|
||||
return a:shifted_pos
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:push2(shifted_pos, target, addition, indent, is_linewise) abort "{{{
|
||||
if a:shifted_pos != s:null_pos
|
||||
let shift = [0, 0, 0, 0]
|
||||
let head = a:target.head2
|
||||
|
||||
if a:is_linewise[1] && a:shifted_pos[1] > head[1]
|
||||
" lnum
|
||||
let shift[1] += 1
|
||||
endif
|
||||
|
||||
if s:lib.is_equal_or_ahead(a:shifted_pos, head)
|
||||
call s:push(shift, a:shifted_pos, head, a:addition[1], a:indent[1], a:is_linewise[1])
|
||||
endif
|
||||
let a:shifted_pos[1:2] += shift[1:2]
|
||||
endif
|
||||
return a:shifted_pos
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:push(shift, shifted_pos, head, addition, indent, is_linewise) abort "{{{
|
||||
let addition = split(a:addition, '\%(\n\|\r\|\r\n\)', 1)
|
||||
|
||||
" lnum
|
||||
let a:shift[1] += len(addition) - 1
|
||||
" column
|
||||
if !a:is_linewise && a:head[1] == a:shifted_pos[1]
|
||||
let a:shift[2] += a:indent + strlen(addition[-1])
|
||||
if len(addition) > 1
|
||||
let a:shift[2] -= a:head[2] - 1
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:pull1(shifted_pos, target, deletion, is_linewise) abort "{{{
|
||||
if a:shifted_pos != s:null_pos
|
||||
let shift = [0, 0, 0, 0]
|
||||
let head = a:target.head1
|
||||
let tail = a:target.tail1
|
||||
|
||||
" lnum
|
||||
if a:shifted_pos[1] > head[1]
|
||||
if a:shifted_pos[1] <= tail[1]
|
||||
let shift[1] -= a:shifted_pos[1] - head[1]
|
||||
else
|
||||
let shift[1] -= tail[1] - head[1]
|
||||
endif
|
||||
endif
|
||||
" column
|
||||
if s:lib.is_ahead(a:shifted_pos, head) && a:shifted_pos[1] <= tail[1]
|
||||
if s:lib.is_ahead(a:shifted_pos, tail)
|
||||
let shift[2] -= strlen(split(a:deletion[0], '\%(\n\|\r\|\r\n\)', 1)[-1])
|
||||
let shift[2] += head[1] != a:shifted_pos[1] ? head[2] - 1 : 0
|
||||
else
|
||||
let shift[2] -= a:shifted_pos[2]
|
||||
let shift[2] += head[2]
|
||||
endif
|
||||
endif
|
||||
|
||||
let a:shifted_pos[1] += shift[1]
|
||||
|
||||
" the case for linewise action
|
||||
if a:is_linewise[0]
|
||||
if a:shifted_pos[1] == head[1]
|
||||
" col
|
||||
let a:shifted_pos[2] = 0
|
||||
endif
|
||||
if a:shifted_pos[1] > head[1]
|
||||
" lnum
|
||||
let a:shifted_pos[1] -= 1
|
||||
endif
|
||||
endif
|
||||
|
||||
if a:shifted_pos[2] == 0
|
||||
let a:shifted_pos[2] = 1
|
||||
elseif a:shifted_pos[2] == s:constants('colmax')
|
||||
let a:shifted_pos[2] = col([a:shifted_pos[1], '$']) - 1
|
||||
let a:shifted_pos[2] += shift[2]
|
||||
else
|
||||
let a:shifted_pos[2] += shift[2]
|
||||
endif
|
||||
endif
|
||||
return a:shifted_pos
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:pull2(shifted_pos, target, deletion, is_linewise) abort "{{{
|
||||
if a:shifted_pos != s:null_pos
|
||||
let shift = [0, 0, 0, 0]
|
||||
let head = a:target.head2
|
||||
let tail = a:target.tail2
|
||||
|
||||
" lnum
|
||||
if a:shifted_pos[1] >= head[1]
|
||||
if a:shifted_pos[1] < tail[1]
|
||||
let shift[1] -= a:shifted_pos[1] - head[1]
|
||||
else
|
||||
let shift[1] -= tail[1] - head[1]
|
||||
endif
|
||||
endif
|
||||
" column
|
||||
if s:lib.is_equal_or_ahead(a:shifted_pos, head) && a:shifted_pos[1] <= tail[1]
|
||||
if s:lib.is_ahead(a:shifted_pos, tail)
|
||||
let shift[2] -= strlen(split(a:deletion[1], '\%(\n\|\r\|\r\n\)', 1)[-1])
|
||||
let shift[2] += head[1] != a:shifted_pos[1] ? head[2] - 1 : 0
|
||||
else
|
||||
let shift[2] -= a:shifted_pos[2] + 1
|
||||
let shift[2] += head[2]
|
||||
endif
|
||||
endif
|
||||
let a:shifted_pos[1:2] += shift[1:2]
|
||||
|
||||
" the case for linewise action
|
||||
if a:is_linewise[1]
|
||||
if a:shifted_pos[1] == head[1]
|
||||
" col
|
||||
let a:shifted_pos[2] = s:constants('colmax')
|
||||
endif
|
||||
if a:shifted_pos[1] >= head[1]
|
||||
" lnum
|
||||
let a:shifted_pos[1] -= 1
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
return a:shifted_pos
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:added_tail(head, tail, linewise) abort "{{{
|
||||
if a:tail[1] > 1 && a:tail[2] == 1
|
||||
let lnum = a:tail[1] - 1
|
||||
let col = col([lnum, '$'])
|
||||
let tail = [a:tail[0], lnum, col, a:tail[3]]
|
||||
elseif a:linewise
|
||||
let tail = a:tail
|
||||
else
|
||||
let tail = s:lib.get_left_pos(a:tail)
|
||||
endif
|
||||
return tail
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,108 @@
|
||||
" common functions
|
||||
|
||||
" variables "{{{
|
||||
" null valiables
|
||||
let s:null_pos = [0, 0, 0, 0]
|
||||
"}}}
|
||||
|
||||
let s:lib = {}
|
||||
function! s:get_wider_region(head_edge, tail_edge) abort "{{{
|
||||
return [s:get_left_pos(a:head_edge), s:get_right_pos(a:tail_edge)]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_left_pos(pos) abort "{{{
|
||||
call setpos('.', a:pos)
|
||||
normal! h
|
||||
return getpos('.')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_right_pos(pos) abort "{{{
|
||||
call setpos('.', a:pos)
|
||||
normal! l
|
||||
return getpos('.')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:c2p(coord) abort "{{{
|
||||
return [0] + a:coord + [0]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_valid_2pos(pos) abort "{{{
|
||||
" NOTE: This function do not check the geometric relationships.
|
||||
" It should be checked by s:is_ahead or s:is_equal_or_ahead
|
||||
" separately.
|
||||
return a:pos.head != s:null_pos && a:pos.tail != s:null_pos
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_valid_4pos(pos) abort "{{{
|
||||
" NOTE: This function do not check the geometric relationships.
|
||||
" It should be checked by s:is_ahead or s:is_equal_or_ahead
|
||||
" separately.
|
||||
return a:pos.head1 != s:null_pos && a:pos.tail1 != s:null_pos
|
||||
\ && a:pos.head2 != s:null_pos && a:pos.tail2 != s:null_pos
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_ahead(pos1, pos2) abort "{{{
|
||||
return a:pos1[1] > a:pos2[1] || (a:pos1[1] == a:pos2[1] && a:pos1[2] > a:pos2[2])
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_equal_or_ahead(pos1, pos2) abort "{{{
|
||||
return a:pos1[1] > a:pos2[1] || (a:pos1[1] == a:pos2[1] && a:pos1[2] >= a:pos2[2])
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_in_between(pos, head, tail) abort "{{{
|
||||
return (a:pos != s:null_pos) && (a:head != s:null_pos) && (a:tail != s:null_pos)
|
||||
\ && ((a:pos[1] > a:head[1]) || ((a:pos[1] == a:head[1]) && (a:pos[2] >= a:head[2])))
|
||||
\ && ((a:pos[1] < a:tail[1]) || ((a:pos[1] == a:tail[1]) && (a:pos[2] <= a:tail[2])))
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_sandwich_option(name, default) abort "{{{
|
||||
if exists('g:operator#sandwich#' . a:name)
|
||||
return eval('g:operator#sandwich#' . a:name)
|
||||
endif
|
||||
if exists('g:sandwich#' . a:name)
|
||||
return eval('g:sandwich#' . a:name)
|
||||
endif
|
||||
return a:default
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_operator_option(name, default) abort "{{{
|
||||
return get(g:, 'operator#sandwich#' . a:name, a:default)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:escape(string) abort "{{{
|
||||
return escape(a:string, '~"\.^$[]*')
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
function! s:export(namelist) abort "{{{
|
||||
let module = {}
|
||||
for name in a:namelist
|
||||
let module[name] = function('s:' . name)
|
||||
endfor
|
||||
return module
|
||||
endfunction
|
||||
"}}}
|
||||
let s:lib = s:export([
|
||||
\ 'get_wider_region',
|
||||
\ 'get_left_pos',
|
||||
\ 'get_right_pos',
|
||||
\ 'c2p',
|
||||
\ 'is_valid_2pos',
|
||||
\ 'is_valid_4pos',
|
||||
\ 'is_ahead',
|
||||
\ 'is_equal_or_ahead',
|
||||
\ 'is_in_between',
|
||||
\ 'get_sandwich_option',
|
||||
\ 'get_operator_option',
|
||||
\ 'escape',
|
||||
\ ])
|
||||
lockvar! s:lib
|
||||
|
||||
function! operator#sandwich#lib#import() abort "{{{
|
||||
return s:lib
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,507 @@
|
||||
" stuff object - managing a line on buffer
|
||||
|
||||
let s:lib = operator#sandwich#lib#import()
|
||||
|
||||
" variables "{{{
|
||||
" null valiables
|
||||
let s:null_coord = [0, 0]
|
||||
let s:null_pos = [0, 0, 0, 0]
|
||||
let s:null_2pos = {
|
||||
\ 'head': copy(s:null_pos),
|
||||
\ 'tail': copy(s:null_pos),
|
||||
\ }
|
||||
let s:null_4pos = {
|
||||
\ 'head1': copy(s:null_pos),
|
||||
\ 'tail1': copy(s:null_pos),
|
||||
\ 'head2': copy(s:null_pos),
|
||||
\ 'tail2': copy(s:null_pos),
|
||||
\ }
|
||||
function! s:SID() abort
|
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$')
|
||||
endfunction
|
||||
let s:SNR = printf("\<SNR>%s_", s:SID())
|
||||
delfunction s:SID
|
||||
|
||||
nnoremap <SID>(v) v
|
||||
|
||||
let s:KEY_v = printf('%s(v)', s:SNR)
|
||||
|
||||
" types
|
||||
let s:type_list = type([])
|
||||
|
||||
" patchs
|
||||
if v:version > 704 || (v:version == 704 && has('patch237'))
|
||||
let s:has_patch_7_4_771 = has('patch-7.4.771')
|
||||
let s:has_patch_7_4_358 = has('patch-7.4.358')
|
||||
else
|
||||
let s:has_patch_7_4_771 = v:version == 704 && has('patch771')
|
||||
let s:has_patch_7_4_358 = v:version == 704 && has('patch358')
|
||||
endif
|
||||
"}}}
|
||||
|
||||
function! operator#sandwich#stuff#new() abort "{{{
|
||||
return deepcopy(s:stuff)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" s:stuff "{{{
|
||||
let s:stuff = {
|
||||
\ 'active' : 1,
|
||||
\ 'edges' : copy(s:null_2pos),
|
||||
\ 'target' : copy(s:null_4pos),
|
||||
\ 'acts' : [],
|
||||
\ 'added' : [],
|
||||
\ }
|
||||
"}}}
|
||||
function! s:stuff.initialize(count, cursor, modmark) dict abort "{{{
|
||||
let self.active = 1
|
||||
let self.acts = map(range(a:count), 'operator#sandwich#act#new()')
|
||||
let self.added = []
|
||||
for act in self.acts
|
||||
call act.initialize(a:cursor, a:modmark, self.added)
|
||||
endfor
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:stuff.set_target() dict abort "{{{
|
||||
if self.active
|
||||
let head = copy(self.edges.head)
|
||||
let tail = copy(self.edges.tail)
|
||||
let self.target.head1 = head
|
||||
let self.target.tail1 = head
|
||||
let self.target.head2 = tail
|
||||
let self.target.tail2 = tail
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:stuff.match(recipes, opt, ...) dict abort "{{{
|
||||
if !self.active
|
||||
return 0
|
||||
endif
|
||||
|
||||
let edges = self.edges
|
||||
let edges_saved = deepcopy(edges)
|
||||
let match_edges = get(a:000, 0, 1)
|
||||
if s:lib.is_valid_2pos(edges) && s:lib.is_ahead(edges.tail, edges.head)
|
||||
let edge_chars = ['', '']
|
||||
if self._match_recipes(a:recipes, a:opt) || (match_edges && self._match_edges(a:recipes, a:opt, edge_chars))
|
||||
" found!
|
||||
return 1
|
||||
else
|
||||
let [head_c, tail_c] = edge_chars
|
||||
if head_c =~# '\s' || tail_c =~# '\s'
|
||||
call self.skip_space()
|
||||
if s:lib.is_ahead(edges.head, edges.tail)
|
||||
" invalid edges after skip spaces
|
||||
let self.active = 0
|
||||
return 0
|
||||
else
|
||||
if self._match_recipes(a:recipes, a:opt, 1) || self._match_edges(a:recipes, a:opt, edge_chars)
|
||||
" found
|
||||
return 1
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
let edges = edges_saved
|
||||
|
||||
" skip characters
|
||||
let target_list = []
|
||||
for candidate in a:recipes
|
||||
call a:opt.update('recipe_delete', candidate)
|
||||
if a:opt.of('skip_char') && has_key(candidate, 'buns')
|
||||
let head = edges.head
|
||||
let tail = edges.tail
|
||||
let opt_regex = a:opt.of('regex')
|
||||
let patterns = s:get_patterns(candidate, opt_regex)
|
||||
let target_list += [s:search_edges(head, tail, patterns)]
|
||||
endif
|
||||
endfor
|
||||
if filter(target_list, 's:lib.is_valid_4pos(v:val)') != []
|
||||
" found
|
||||
let self.target = s:shortest(target_list)
|
||||
return 1
|
||||
endif
|
||||
|
||||
call a:opt.clear('recipe_delete')
|
||||
endif
|
||||
let self.active = 0
|
||||
return 0
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:stuff._match_recipes(recipes, opt, ...) dict abort "{{{
|
||||
let is_space_skipped = get(a:000, 0, 0)
|
||||
|
||||
let found = 0
|
||||
for candidate in a:recipes
|
||||
call a:opt.update('recipe_delete', candidate)
|
||||
if !is_space_skipped || a:opt.of('skip_space')
|
||||
if has_key(candidate, 'buns')
|
||||
" search buns
|
||||
let patterns = s:get_patterns(candidate, a:opt.of('regex'))
|
||||
let target = s:check_edges(self.edges.head, self.edges.tail, patterns)
|
||||
elseif has_key(candidate, 'external')
|
||||
" get difference of external motion/textobject
|
||||
let target = s:check_textobj_diff(self.edges.head, self.edges.tail, candidate, a:opt.of('noremap', 'recipe_delete'))
|
||||
else
|
||||
let target = deepcopy(s:null_4pos)
|
||||
endif
|
||||
|
||||
if s:lib.is_valid_4pos(target)
|
||||
let found = 1
|
||||
let self.target = target
|
||||
break
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
return found
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:stuff._match_edges(recipes, opt, edge_chars) dict abort "{{{
|
||||
let head = self.edges.head
|
||||
let tail = self.edges.tail
|
||||
let head_c = s:get_cursorchar(head)
|
||||
let tail_c = s:get_cursorchar(tail)
|
||||
|
||||
let found = 0
|
||||
if head_c ==# tail_c
|
||||
" check duplicate with recipe
|
||||
for candidate in a:recipes
|
||||
call a:opt.update('recipe_delete', candidate)
|
||||
if has_key(candidate, 'buns') && !a:opt.of('regex')
|
||||
\ && candidate.buns[0] ==# head_c
|
||||
\ && candidate.buns[1] ==# tail_c
|
||||
" The pair has already checked by a recipe.
|
||||
return 0
|
||||
endif
|
||||
endfor
|
||||
|
||||
" both edges are matched
|
||||
call a:opt.clear('recipe_delete')
|
||||
if !(a:opt.of('skip_space') == 2 && head_c =~# '\s')
|
||||
let found = 1
|
||||
let self.target = {
|
||||
\ 'head1': head, 'tail1': head,
|
||||
\ 'head2': tail, 'tail2': tail,
|
||||
\ }
|
||||
endif
|
||||
endif
|
||||
|
||||
let a:edge_chars[0] = head_c
|
||||
let a:edge_chars[1] = tail_c
|
||||
return found
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:stuff.skip_space() dict abort "{{{
|
||||
if self.active
|
||||
call s:skip_space(self.edges.head, self.edges.tail)
|
||||
if s:lib.is_ahead(self.edges.head, self.edges.tail)
|
||||
let self.active = 0
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:stuff.hi_list(place, linewise) dict abort "{{{
|
||||
if !self.active
|
||||
return []
|
||||
endif
|
||||
|
||||
let orderlist = []
|
||||
if a:place ==# 'target'
|
||||
let orderlist += [[self.target, [a:linewise, a:linewise]]]
|
||||
elseif a:place ==# 'added'
|
||||
for added in self.added
|
||||
let orderlist += [[added, added.linewise]]
|
||||
endfor
|
||||
elseif a:place ==# 'stuff'
|
||||
let stuff = {'head1': self.edges.head, 'tail1': self.edges.tail, 'head2': copy(s:null_pos), 'tail2': copy(s:null_pos)}
|
||||
let orderlist += [[stuff, [0, 0]]]
|
||||
endif
|
||||
return orderlist
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" private functions
|
||||
function! s:check_edges(head, tail, patterns) abort "{{{
|
||||
if a:patterns[0] ==# '' || a:patterns[1] ==# '' | return s:null_4pos | endif
|
||||
|
||||
call setpos('.', a:head)
|
||||
let head1 = searchpos(a:patterns[0], 'c', a:tail[1])
|
||||
|
||||
if head1 != a:head[1:2] | return s:null_4pos | endif
|
||||
|
||||
call setpos('.', a:tail)
|
||||
let tail2 = s:searchpos_bce(a:tail, a:patterns[1], a:head[1])
|
||||
|
||||
if tail2 != a:tail[1:2] | return s:null_4pos | endif
|
||||
|
||||
let head2 = searchpos(a:patterns[1], 'bc', a:head[1])
|
||||
call setpos('.', a:head)
|
||||
let tail1 = searchpos(a:patterns[0], 'ce', a:tail[1])
|
||||
|
||||
if head1 == s:null_coord || tail1 == s:null_coord
|
||||
\ || head2 == s:null_coord || tail2 == s:null_coord
|
||||
\ || s:lib.is_equal_or_ahead(s:lib.c2p(tail1), s:lib.c2p(head2))
|
||||
return s:null_4pos
|
||||
endif
|
||||
|
||||
let target = {
|
||||
\ 'head1': head1, 'tail1': tail1,
|
||||
\ 'head2': head2, 'tail2': tail2,
|
||||
\ }
|
||||
return map(target, 's:lib.c2p(v:val)')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:search_edges(head, tail, patterns) abort "{{{
|
||||
if a:patterns[0] ==# '' || a:patterns[1] ==# '' | return s:null_4pos | endif
|
||||
|
||||
call setpos('.', a:head)
|
||||
let head1 = searchpos(a:patterns[0], 'c', a:tail[1])
|
||||
|
||||
call setpos('.', a:tail)
|
||||
let tail2 = s:searchpos_bce(a:tail, a:patterns[1], a:head[1])
|
||||
|
||||
if head1 == s:null_coord || tail2 == s:null_coord
|
||||
\ || s:lib.is_equal_or_ahead(s:lib.c2p(head1), s:lib.c2p(tail2))
|
||||
return s:null_4pos
|
||||
endif
|
||||
|
||||
let head2 = searchpos(a:patterns[1], 'bc', head1[0])
|
||||
call setpos('.', s:lib.c2p(head1))
|
||||
let tail1 = searchpos(a:patterns[0], 'ce', head2[0])
|
||||
|
||||
if tail1 == s:null_coord || head2 == s:null_coord
|
||||
\ || s:lib.is_ahead(s:lib.c2p(head1), s:lib.c2p(tail1))
|
||||
\ || s:lib.is_ahead(s:lib.c2p(head2), s:lib.c2p(tail2))
|
||||
\ || s:lib.is_equal_or_ahead(s:lib.c2p(tail1), s:lib.c2p(head2))
|
||||
return s:null_4pos
|
||||
endif
|
||||
|
||||
let target = {
|
||||
\ 'head1': head1, 'tail1': tail1,
|
||||
\ 'head2': head2, 'tail2': tail2,
|
||||
\ }
|
||||
return map(target, 's:lib.c2p(v:val)')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:check_textobj_diff(head, tail, candidate, opt_noremap) abort "{{{
|
||||
let target = deepcopy(s:null_4pos)
|
||||
if has_key(a:candidate, 'excursus')
|
||||
let coord = a:candidate.excursus.coord
|
||||
let target.head1 = s:lib.c2p(coord.head)
|
||||
let target.tail1 = s:lib.get_left_pos(s:lib.c2p(coord.inner_head))
|
||||
let target.head2 = s:lib.get_right_pos(s:lib.c2p(coord.inner_tail))
|
||||
let target.tail2 = s:lib.c2p(coord.tail)
|
||||
|
||||
if target.head1 == a:head && target.tail2 == a:tail
|
||||
\ && target.tail1 != s:null_pos && target.head2 != s:null_pos
|
||||
\ && s:lib.is_equal_or_ahead(target.tail1, target.head1)
|
||||
\ && s:lib.is_equal_or_ahead(target.tail2, target.head2)
|
||||
return target
|
||||
endif
|
||||
endif
|
||||
|
||||
let [textobj_i, textobj_a] = a:candidate.external
|
||||
let [visual_head, visual_tail] = [getpos("'<"), getpos("'>")]
|
||||
let visualmode = visualmode()
|
||||
if a:opt_noremap
|
||||
let cmd = 'silent! normal!'
|
||||
let v = 'v'
|
||||
else
|
||||
let cmd = 'silent! normal'
|
||||
let v = s:KEY_v
|
||||
endif
|
||||
|
||||
let order_list = [[1, a:head], [1, a:tail]]
|
||||
if has_key(a:candidate, 'excursus')
|
||||
let order_list = [[a:candidate.excursus.count, a:candidate.excursus.cursor]] + order_list
|
||||
endif
|
||||
|
||||
let found = 0
|
||||
for [l:count, cursor] in order_list
|
||||
" get outer positions
|
||||
let [target.head1, target.tail2, motionwise_a] = s:get_textobj_region(cursor, cmd, v, l:count, textobj_a)
|
||||
|
||||
" get inner positions
|
||||
let [target.tail1, target.head2, motionwise_i] = s:get_textobj_region(cursor, cmd, v, l:count, textobj_i)
|
||||
if motionwise_i ==# "\<C-v>"
|
||||
normal! gv
|
||||
if getpos('.')[1] == target.head2[1]
|
||||
normal! O
|
||||
let target.tail1 = getpos('.')
|
||||
normal! o
|
||||
let target.head2 = getpos('.')
|
||||
else
|
||||
normal! O
|
||||
let target.head2 = getpos('.')
|
||||
normal! o
|
||||
let target.tail1 = getpos('.')
|
||||
endif
|
||||
execute "normal! \<Esc>"
|
||||
endif
|
||||
|
||||
" check validity
|
||||
if target.head1 == a:head && target.tail2 == a:tail
|
||||
\ && target.tail1 != s:null_pos && target.head2 != s:null_pos
|
||||
\ && s:lib.is_ahead(target.tail1, target.head1)
|
||||
\ && s:lib.is_ahead(target.tail2, target.head2)
|
||||
let [target.tail1, target.head2] = s:lib.get_wider_region(target.tail1, target.head2)
|
||||
let found = 1
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
|
||||
" restore visualmode
|
||||
if visualmode ==# ''
|
||||
call visualmode(1)
|
||||
else
|
||||
execute 'normal! ' . visualmode
|
||||
execute 'normal! ' . "\<Esc>"
|
||||
endif
|
||||
" restore marks
|
||||
call setpos("'<", visual_head)
|
||||
call setpos("'>", visual_tail)
|
||||
|
||||
if found
|
||||
return target
|
||||
else
|
||||
return s:null_4pos
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_textobj_region(cursor, cmd, visualmode, count, key_seq) abort "{{{
|
||||
call setpos('.', a:cursor)
|
||||
execute printf('%s %s%d%s', a:cmd, a:visualmode, a:count, a:key_seq)
|
||||
if mode() ==? 'v' || mode() ==# "\<C-v>"
|
||||
execute "normal! \<Esc>"
|
||||
else
|
||||
return [copy(s:null_coord), copy(s:null_coord), a:visualmode]
|
||||
endif
|
||||
let visualmode = visualmode()
|
||||
let [head, tail] = [getpos("'<"), getpos("'>")]
|
||||
" NOTE: V never comes for v. Thus if head == tail == self.cursor, then
|
||||
" it is failed.
|
||||
if head == a:cursor && tail == a:cursor
|
||||
let [head, tail] = [copy(s:null_pos), copy(s:null_pos)]
|
||||
elseif visualmode ==# 'V'
|
||||
let tail[2] = col([tail[1], '$'])
|
||||
endif
|
||||
return [head, tail, visualmode]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_patterns(candidate, opt_regex) abort "{{{
|
||||
let patterns = deepcopy(a:candidate.buns)
|
||||
if !a:opt_regex
|
||||
let patterns = map(patterns, 's:lib.escape(v:val)')
|
||||
endif
|
||||
|
||||
" substitute a break "\n" to a regular expression pattern '\n'
|
||||
let patterns = map(patterns, 'substitute(v:val, ''\n'', ''\\n'', ''g'')')
|
||||
return patterns
|
||||
endfunction
|
||||
"}}}
|
||||
" function! s:searchpos_bce(curpos, pattern, stopline) "{{{
|
||||
if s:has_patch_7_4_771
|
||||
function! s:searchpos_bce(curpos, pattern, stopline) abort
|
||||
return searchpos(a:pattern, 'bce', a:stopline)
|
||||
endfunction
|
||||
else
|
||||
" workaround for unicode string (because of a bug of vim)
|
||||
" If the cursor is on a unicode character(uc), searchpos(uc, 'bce', stopline) always returns [0, 0],
|
||||
" though searchpos(uc, 'bce') returns a correct value.
|
||||
function! s:searchpos_bce(curpos, pattern, stopline) abort
|
||||
if a:curpos[1] == line('$') && a:curpos[2] == col([line('$'), '$'])
|
||||
silent! normal! h
|
||||
return searchpos(a:pattern, 'e', a:stopline)
|
||||
else
|
||||
silent! normal! l
|
||||
return searchpos(a:pattern, 'be', a:stopline)
|
||||
endif
|
||||
endfunction
|
||||
endif
|
||||
"}}}
|
||||
function! s:get_cursorchar(pos) abort "{{{
|
||||
let reg = ['"', getreg('"'), getregtype('"')]
|
||||
try
|
||||
call setpos('.', a:pos)
|
||||
silent noautocmd normal! yl
|
||||
let c = @@
|
||||
finally
|
||||
call call('setreg', reg)
|
||||
endtry
|
||||
return c
|
||||
endfunction
|
||||
"}}}
|
||||
" function! s:shortest(list) abort "{{{
|
||||
if s:has_patch_7_4_358
|
||||
function! s:shortest(list) abort
|
||||
call map(a:list, '[v:val, s:get_buf_length(v:val.head1, v:val.tail2)]')
|
||||
call sort(a:list, 's:compare_buf_length')
|
||||
return a:list[0][0]
|
||||
endfunction
|
||||
|
||||
function! s:compare_buf_length(i1, i2) abort
|
||||
return a:i2[1] - a:i1[1]
|
||||
endfunction
|
||||
else
|
||||
function! s:shortest(list) abort
|
||||
call map(a:list, '[v:val, s:get_buf_length(v:val.head1, v:val.tail2)]')
|
||||
let len = len(a:list)
|
||||
let min = len - 1
|
||||
if len - 2 >= 0
|
||||
for i in range(len - 2, 0, -1)
|
||||
if a:list[min][1] >= a:list[i][1]
|
||||
let min = i
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
return a:list[min][0]
|
||||
endfunction
|
||||
endif
|
||||
"}}}
|
||||
function! s:get_buf_length(start, end) abort "{{{
|
||||
if a:start[1] == a:end[1]
|
||||
let len = a:end[2] - a:start[2] + 1
|
||||
else
|
||||
let length_list = map(getline(a:start[1], a:end[1]), 'len(v:val) + 1')
|
||||
let idx = 0
|
||||
let accumm_length = 0
|
||||
let accumm_list = [0]
|
||||
for length in length_list[1:]
|
||||
let accumm_length = accumm_length + length_list[idx]
|
||||
let accumm_list += [accumm_length]
|
||||
let idx += 1
|
||||
endfor
|
||||
let len = accumm_list[a:end[1] - a:start[1]] + a:end[2] - a:start[2] + 1
|
||||
endif
|
||||
return len
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:skip_space(head, tail) abort "{{{
|
||||
" NOTE: This function is destructive, directly update a:head and a:tail.
|
||||
call setpos('.', a:head)
|
||||
if a:head[2] == col([a:head[1], '$'])
|
||||
" if the cursor is on a line breaking, it should not be skipped.
|
||||
let head = a:head
|
||||
else
|
||||
let head = s:lib.c2p(searchpos('\_S', 'c', a:tail[1]))
|
||||
endif
|
||||
|
||||
call setpos('.', a:tail)
|
||||
if a:tail[2] == 1
|
||||
let tail = a:tail
|
||||
else
|
||||
let tail = s:lib.c2p(searchpos('\_S', 'bc', a:head[1]))
|
||||
endif
|
||||
|
||||
if head != s:null_pos && tail != s:null_pos && s:lib.is_equal_or_ahead(tail, head)
|
||||
let a:head[0:3] = head[0:3]
|
||||
let a:tail[0:3] = tail[0:3]
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,52 @@
|
||||
" prepare recipes
|
||||
function! sandwich#get_recipes() abort "{{{
|
||||
if exists('b:sandwich_recipes')
|
||||
let recipes = b:sandwich_recipes
|
||||
elseif exists('g:sandwich#recipes')
|
||||
let recipes = g:sandwich#recipes
|
||||
else
|
||||
let recipes = g:sandwich#default_recipes
|
||||
endif
|
||||
return deepcopy(recipes)
|
||||
endfunction
|
||||
"}}}
|
||||
if exists('g:sandwich#default_recipes')
|
||||
unlockvar! g:sandwich#default_recipes
|
||||
endif
|
||||
let g:sandwich#default_recipes = [
|
||||
\ {'buns': ['\s\+', '\s\+'], 'regex': 1, 'kind': ['delete', 'replace', 'query'], 'input': [' ']},
|
||||
\ {'buns': ['', ''], 'action': ['add'], 'motionwise': ['line'], 'linewise': 1, 'input': ["\<CR>"]},
|
||||
\ {'buns': ['^$', '^$'], 'regex': 1, 'linewise': 1, 'input': ["\<CR>"]},
|
||||
\ {'buns': ['<', '>'], 'expand_range': 0},
|
||||
\ {'buns': ['"', '"'], 'quoteescape': 1, 'expand_range': 0, 'nesting': 0, 'linewise': 0},
|
||||
\ {'buns': ["'", "'"], 'quoteescape': 1, 'expand_range': 0, 'nesting': 0, 'linewise': 0},
|
||||
\ {'buns': ["`", "`"], 'quoteescape': 1, 'expand_range': 0, 'nesting': 0, 'linewise': 0},
|
||||
\ {'buns': ['{', '}'], 'nesting': 1, 'skip_break': 1},
|
||||
\ {'buns': ['[', ']'], 'nesting': 1},
|
||||
\ {'buns': ['(', ')'], 'nesting': 1},
|
||||
\ {'buns': 'sandwich#magicchar#t#tag()', 'listexpr': 1, 'kind': ['add'], 'action': ['add'], 'input': ['t', 'T']},
|
||||
\ {'buns': 'sandwich#magicchar#t#tag()', 'listexpr': 1, 'kind': ['replace'], 'action': ['add'], 'input': ['T']},
|
||||
\ {'buns': 'sandwich#magicchar#t#tagname()', 'listexpr': 1, 'kind': ['replace'], 'action': ['add'], 'input': ['t']},
|
||||
\ {'external': ["\<Plug>(textobj-sandwich-tag-i)", "\<Plug>(textobj-sandwich-tag-a)"], 'noremap' : 0, 'kind' : ['delete', 'textobj'], 'expr_filter': ['operator#sandwich#kind() !=# "replace"'], 'linewise': 1, 'input': ['t', 'T']},
|
||||
\ {'external': ["\<Plug>(textobj-sandwich-tag-i)", "\<Plug>(textobj-sandwich-tag-a)"], 'noremap' : 0, 'kind' : ['replace', 'query'], 'expr_filter': ['operator#sandwich#kind() ==# "replace"'], 'input': ['T']},
|
||||
\ {'external': ["\<Plug>(textobj-sandwich-tagname-i)", "\<Plug>(textobj-sandwich-tagname-a)"], 'noremap' : 0, 'kind' : ['replace', 'textobj'], 'expr_filter': ['operator#sandwich#kind() ==# "replace"'], 'input': ['t']},
|
||||
\ {'buns': ['sandwich#magicchar#f#fname()', '")"'], 'kind': ['add', 'replace'], 'action': ['add'], 'expr': 1, 'input': ['f']},
|
||||
\ {'external': ["\<Plug>(textobj-sandwich-function-ip)", "\<Plug>(textobj-sandwich-function-i)"], 'noremap': 0, 'kind': ['delete', 'replace', 'query'], 'input': ['f']},
|
||||
\ {'external': ["\<Plug>(textobj-sandwich-function-ap)", "\<Plug>(textobj-sandwich-function-a)"], 'noremap': 0, 'kind': ['delete', 'replace', 'query'], 'input': ['F']},
|
||||
\ {'buns': 'sandwich#magicchar#i#input("operator")', 'kind': ['add', 'replace'], 'action': ['add'], 'listexpr': 1, 'input': ['i']},
|
||||
\ {'buns': 'sandwich#magicchar#i#input("textobj", 1)', 'kind': ['delete', 'replace', 'query'], 'listexpr': 1, 'input': ['i']},
|
||||
\ {'buns': 'sandwich#magicchar#i#lastinput("operator", 1)', 'kind': ['add', 'replace'], 'action': ['add'], 'listexpr': 1, 'input': ['I']},
|
||||
\ {'buns': 'sandwich#magicchar#i#lastinput("textobj")', 'kind': ['delete', 'replace', 'query'], 'listexpr': 1, 'input': ['I']},
|
||||
\ ]
|
||||
lockvar! g:sandwich#default_recipes
|
||||
|
||||
let g:sandwich#jsx_filetypes = [
|
||||
\ 'javascript.jsx',
|
||||
\ 'typescript.tsx',
|
||||
\ 'javascriptreact',
|
||||
\ 'typescriptreact'
|
||||
\ ]
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,60 @@
|
||||
" clock object - measuring elapsed time in a operation
|
||||
|
||||
" variables "{{{
|
||||
" features
|
||||
let s:has_reltime_and_float = has('reltime') && has('float')
|
||||
"}}}
|
||||
|
||||
function! sandwich#clock#new() abort "{{{
|
||||
return deepcopy(s:clock)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" s:clock "{{{
|
||||
let s:clock = {
|
||||
\ 'started' : 0,
|
||||
\ 'paused' : 0,
|
||||
\ 'zerotime': reltime(),
|
||||
\ 'stoptime': reltime(),
|
||||
\ 'losstime': 0,
|
||||
\ }
|
||||
"}}}
|
||||
function! s:clock.start() dict abort "{{{
|
||||
if self.started
|
||||
if self.paused
|
||||
let self.losstime += str2float(reltimestr(reltime(self.stoptime)))
|
||||
let self.paused = 0
|
||||
endif
|
||||
else
|
||||
if s:has_reltime_and_float
|
||||
let self.zerotime = reltime()
|
||||
let self.started = 1
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:clock.pause() dict abort "{{{
|
||||
let self.stoptime = reltime()
|
||||
let self.paused = 1
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:clock.elapsed() dict abort "{{{
|
||||
if self.started
|
||||
let total = str2float(reltimestr(reltime(self.zerotime)))
|
||||
return floor((total - self.losstime)*1000)
|
||||
else
|
||||
return 0
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:clock.stop() dict abort "{{{
|
||||
let self.started = 0
|
||||
let self.paused = 0
|
||||
let self.losstime = 0
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,42 @@
|
||||
" constants - storing essential constants
|
||||
|
||||
" variables "{{{
|
||||
" types
|
||||
let s:type_list = type([])
|
||||
"}}}
|
||||
|
||||
function! sandwich#constants#get(name) abort "{{{
|
||||
return type(a:name) == s:type_list
|
||||
\ ? map(copy(a:name), 's:constants[v:val]()')
|
||||
\ : s:constants[a:name]()
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" s:constants "{{{
|
||||
let s:constants = {}
|
||||
"}}}
|
||||
" The maximum number of columns "{{{
|
||||
function! s:constants.colmax() dict abort
|
||||
return s:colmax_obtained ? s:colmax : s:colmax()
|
||||
endfunction
|
||||
|
||||
let s:colmax = 2147483647 " default value in many cases
|
||||
let s:colmax_obtained = 0
|
||||
function! s:colmax() abort
|
||||
let view = winsaveview()
|
||||
try
|
||||
normal! $
|
||||
let colmax = winsaveview().curswant
|
||||
call winrestview(view)
|
||||
let s:colmax_obtained = 1
|
||||
catch
|
||||
let colmax = s:colmax
|
||||
let s:colmax_obtained = 0
|
||||
endtry
|
||||
return colmax
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,34 @@
|
||||
let g:sandwich#filetype#tex#environments = get(g:, 'sandwich#filetype#tex#environments', [
|
||||
\ 'array', 'center', 'description', 'enumerate', 'eqnarray', 'equation',
|
||||
\ 'equation*', 'figure', 'flushleft', 'flushright', 'itemize', 'list',
|
||||
\ 'minipage', 'picture', 'quotation', 'quote', 'tabbing', 'table',
|
||||
\ 'tabular', 'tabular*', 'thebibliography', 'theorem', 'titlepage',
|
||||
\ 'verbatim', 'verse'
|
||||
\ ])
|
||||
|
||||
function! sandwich#filetype#tex#EnvCompl(argread, cmdline, cursorpos) abort
|
||||
let n = strlen(a:argread)
|
||||
if exists('b:sandwich#filetype#tex#environments')
|
||||
let list = copy(b:sandwich#filetype#tex#environments)
|
||||
else
|
||||
let list = copy(g:sandwich#filetype#tex#environments)
|
||||
endif
|
||||
if n > 0
|
||||
let list = filter(list, 'v:val[: n-1] ==# a:argread')
|
||||
endif
|
||||
return list
|
||||
endfunction
|
||||
|
||||
function! sandwich#filetype#tex#EnvInput() abort
|
||||
echohl MoreMsg
|
||||
let env = input('Environment-name: ', '', 'customlist,sandwich#filetype#tex#EnvCompl')
|
||||
echohl NONE
|
||||
return [printf('\begin{%s}', env), printf('\end{%s}', env)]
|
||||
endfunction
|
||||
|
||||
function! sandwich#filetype#tex#CmdInput() abort
|
||||
echohl MoreMsg
|
||||
let cmd = input('Command: ', '')
|
||||
echohl NONE
|
||||
return [printf('\%s{', cmd), '}']
|
||||
endfunction
|
||||
@ -0,0 +1,514 @@
|
||||
" highlight object - managing highlight on a buffer
|
||||
|
||||
" variables "{{{
|
||||
" null valiables
|
||||
let s:null_pos = [0, 0, 0, 0]
|
||||
|
||||
" types
|
||||
let s:type_list = type([])
|
||||
|
||||
" patchs
|
||||
if v:version > 704 || (v:version == 704 && has('patch237'))
|
||||
let s:has_patch_7_4_362 = has('patch-7.4.362')
|
||||
let s:has_patch_7_4_392 = has('patch-7.4.392')
|
||||
else
|
||||
let s:has_patch_7_4_362 = v:version == 704 && has('patch362')
|
||||
let s:has_patch_7_4_392 = v:version == 704 && has('patch392')
|
||||
endif
|
||||
|
||||
" features
|
||||
let s:has_gui_running = has('gui_running')
|
||||
let s:has_window_ID = exists('*win_getid')
|
||||
|
||||
" SID
|
||||
function! s:SID() abort
|
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$')
|
||||
endfunction
|
||||
let s:SID = printf("\<SNR>%s_", s:SID())
|
||||
delfunction s:SID
|
||||
"}}}
|
||||
|
||||
function! sandwich#highlight#new() abort "{{{
|
||||
return deepcopy(s:highlight)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" s:highlight "{{{
|
||||
let s:highlight = {
|
||||
\ 'status': 0,
|
||||
\ 'group' : '',
|
||||
\ 'id' : [],
|
||||
\ 'order_list': [],
|
||||
\ 'region': {},
|
||||
\ 'linewise': 0,
|
||||
\ 'bufnr': 0,
|
||||
\ 'winid': 0,
|
||||
\ }
|
||||
"}}}
|
||||
function! s:highlight.initialize() dict abort "{{{
|
||||
call self.quench()
|
||||
let self.status = 0
|
||||
let self.group = ''
|
||||
let self.id = []
|
||||
let self.order_list = []
|
||||
let self.region = {}
|
||||
let self.linewise = 0
|
||||
let self.bufnr = 0
|
||||
let self.winid = 0
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:highlight.order(target, linewise) dict abort "{{{
|
||||
let order = []
|
||||
let order_list = []
|
||||
for [head, tail, linewise] in [[a:target.head1, a:target.tail1, a:linewise[0]],
|
||||
\ [a:target.head2, a:target.tail2, a:linewise[1]]]
|
||||
if linewise
|
||||
call s:highlight_order_linewise(order_list, order, head, tail)
|
||||
else
|
||||
call s:highlight_order_charwise(order_list, order, head, tail)
|
||||
endif
|
||||
endfor
|
||||
if order != []
|
||||
call add(order_list, order)
|
||||
endif
|
||||
let self.order_list += order_list
|
||||
let self.region = deepcopy(a:target)
|
||||
let self.linewise = a:linewise
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:highlight.show(...) dict abort "{{{
|
||||
if self.order_list == []
|
||||
return 0
|
||||
endif
|
||||
|
||||
if a:0 < 1
|
||||
if self.group ==# ''
|
||||
return 0
|
||||
else
|
||||
let hi_group = self.group
|
||||
endif
|
||||
else
|
||||
let hi_group = a:1
|
||||
endif
|
||||
|
||||
if self.status
|
||||
if hi_group ==# self.group
|
||||
return 0
|
||||
else
|
||||
call self.quench()
|
||||
endif
|
||||
endif
|
||||
|
||||
for order in self.order_list
|
||||
let self.id += s:matchaddpos(hi_group, order)
|
||||
endfor
|
||||
call filter(self.id, 'v:val > 0')
|
||||
let self.status = 1
|
||||
let self.group = hi_group
|
||||
let self.bufnr = bufnr('%')
|
||||
if s:has_window_ID
|
||||
let self.winid = win_getid()
|
||||
endif
|
||||
return 1
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:highlight.quench() dict abort "{{{
|
||||
if !self.status
|
||||
return 0
|
||||
endif
|
||||
|
||||
let tabnr = tabpagenr()
|
||||
let winnr = winnr()
|
||||
let view = winsaveview()
|
||||
if self.highlighted_window()
|
||||
call s:matchdelete_all(self.id)
|
||||
let self.status = 0
|
||||
return 1
|
||||
endif
|
||||
|
||||
if s:is_in_cmdline_window() || s:is_in_popup_terminal_window()
|
||||
let s:paused += [self]
|
||||
augroup sandwich-pause-quenching
|
||||
autocmd!
|
||||
autocmd WinEnter * call s:got_out_of_cmdwindow()
|
||||
augroup END
|
||||
return 0
|
||||
endif
|
||||
|
||||
if self.goto_highlighted_window()
|
||||
call s:matchdelete_all(self.id)
|
||||
else
|
||||
call filter(self.id, 0)
|
||||
endif
|
||||
let self.status = 0
|
||||
call s:goto_window(winnr, tabnr, view)
|
||||
return 1
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:highlight.quench_timer(time) dict abort "{{{
|
||||
let id = timer_start(a:time, s:SID . 'quench')
|
||||
let s:quench_table[string(id)] = self
|
||||
" this is called when user gets control again
|
||||
call timer_start(1, {-> s:set_autocmds(id)})
|
||||
return id
|
||||
endfunction
|
||||
"}}}
|
||||
" function! s:highlight.highlighted_window() dict abort "{{{
|
||||
if s:has_window_ID
|
||||
function! s:highlight.highlighted_window() dict abort
|
||||
return self.winid == win_getid()
|
||||
endfunction
|
||||
else
|
||||
function! s:highlight.highlighted_window() dict abort
|
||||
if self.id == []
|
||||
return 0
|
||||
endif
|
||||
|
||||
let id = self.id[0]
|
||||
return filter(getmatches(), 'v:val.id == id') != [] ? 1 : 0
|
||||
endfunction
|
||||
endif
|
||||
"}}}
|
||||
" function! s:highlight.goto_highlighted_window() dict abort "{{{
|
||||
if s:has_window_ID
|
||||
function! s:highlight.goto_highlighted_window() dict abort
|
||||
noautocmd let reached = win_gotoid(self.winid)
|
||||
return reached
|
||||
endfunction
|
||||
else
|
||||
function! s:highlight.goto_highlighted_window() dict abort
|
||||
return s:search_highlighted_windows(self.id, tabpagenr()) != [0, 0]
|
||||
endfunction
|
||||
endif
|
||||
"}}}
|
||||
|
||||
" for delayed quenching "{{{
|
||||
let s:quench_table = {}
|
||||
let s:paused = []
|
||||
function! s:quench(id) abort "{{{
|
||||
let options = s:shift_options()
|
||||
let highlight = s:get(a:id)
|
||||
try
|
||||
if highlight != {}
|
||||
call highlight.quench()
|
||||
endif
|
||||
catch /^Vim\%((\a\+)\)\=:E523/
|
||||
" NOTE: For "textlock"
|
||||
if highlight != {}
|
||||
call highlight.quench_timer(50)
|
||||
endif
|
||||
return 1
|
||||
finally
|
||||
unlet s:quench_table[a:id]
|
||||
call timer_stop(a:id)
|
||||
call s:clear_autocmds()
|
||||
call s:restore_options(options)
|
||||
endtry
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get(id) abort "{{{
|
||||
return get(s:quench_table, string(a:id), {})
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#highlight#cancel(...) abort "{{{
|
||||
if a:0 > 0
|
||||
let id_list = type(a:1) == s:type_list ? a:1 : a:000
|
||||
else
|
||||
let id_list = map(keys(s:quench_table), 'str2nr(v:val)')
|
||||
endif
|
||||
|
||||
for id in id_list
|
||||
call s:quench(id)
|
||||
endfor
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:quench_paused(...) abort "{{{
|
||||
if s:is_in_cmdline_window() || s:is_in_popup_terminal_window()
|
||||
return
|
||||
endif
|
||||
|
||||
augroup sandwich-pause-quenching
|
||||
autocmd!
|
||||
augroup END
|
||||
|
||||
for highlight in s:paused
|
||||
call highlight.quench()
|
||||
endfor
|
||||
let s:paused = []
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:got_out_of_cmdwindow() abort "{{{
|
||||
augroup sandwich-pause-quenching
|
||||
autocmd!
|
||||
autocmd CursorMoved * call s:quench_paused()
|
||||
augroup END
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:set_autocmds(id) abort "{{{
|
||||
augroup sandwich-highlight
|
||||
autocmd!
|
||||
execute printf('autocmd TextChanged,InsertEnter,BufUnload <buffer> call s:cancel_highlight(%s)', a:id)
|
||||
execute printf('autocmd BufEnter * call s:switch_highlight(%s)', a:id)
|
||||
augroup END
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:clear_autocmds() abort "{{{
|
||||
augroup sandwich-highlight
|
||||
autocmd!
|
||||
augroup END
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:cancel_highlight(id) abort "{{{
|
||||
call s:quench(a:id)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:switch_highlight(id) abort "{{{
|
||||
let highlight = s:get(a:id)
|
||||
if highlight == {} || !highlight.highlighted_window()
|
||||
return
|
||||
endif
|
||||
|
||||
if highlight.bufnr == bufnr('%')
|
||||
call highlight.show()
|
||||
else
|
||||
call highlight.quench()
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
"}}}
|
||||
|
||||
" private functions
|
||||
function! s:highlight_order_charwise(order_list, order, head, tail) abort "{{{
|
||||
let n = len(a:order)
|
||||
if a:head != s:null_pos && a:tail != s:null_pos && s:is_equal_or_ahead(a:tail, a:head)
|
||||
if a:head[1] == a:tail[1]
|
||||
call add(a:order, a:head[1:2] + [a:tail[2] - a:head[2] + 1])
|
||||
let n += 1
|
||||
else
|
||||
for lnum in range(a:head[1], a:tail[1])
|
||||
if lnum == a:head[1]
|
||||
call add(a:order, a:head[1:2] + [col([a:head[1], '$']) - a:head[2] + 1])
|
||||
elseif lnum == a:tail[1]
|
||||
call add(a:order, [a:tail[1], 1] + [a:tail[2]])
|
||||
else
|
||||
call add(a:order, [lnum])
|
||||
endif
|
||||
|
||||
if n == 7
|
||||
call add(a:order_list, deepcopy(a:order))
|
||||
call filter(a:order, 0)
|
||||
let n = 0
|
||||
else
|
||||
let n += 1
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:highlight_order_linewise(order_list, order, head, tail) abort "{{{
|
||||
let n = len(a:order)
|
||||
if a:head != s:null_pos && a:tail != s:null_pos && a:head[1] <= a:tail[1]
|
||||
for lnum in range(a:head[1], a:tail[1])
|
||||
call add(a:order, [lnum])
|
||||
if n == 7
|
||||
call add(a:order_list, deepcopy(a:order))
|
||||
call filter(a:order, 0)
|
||||
let n = 0
|
||||
else
|
||||
let n += 1
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
" function! s:matchaddpos(group, pos) abort "{{{
|
||||
if s:has_patch_7_4_362
|
||||
function! s:matchaddpos(group, pos) abort
|
||||
return [matchaddpos(a:group, a:pos)]
|
||||
endfunction
|
||||
else
|
||||
function! s:matchaddpos(group, pos) abort
|
||||
let id_list = []
|
||||
for pos in a:pos
|
||||
if len(pos) == 1
|
||||
let id_list += [matchadd(a:group, printf('\%%%dl', pos[0]))]
|
||||
else
|
||||
let id_list += [matchadd(a:group, printf('\%%%dl\%%>%dc.*\%%<%dc', pos[0], pos[1]-1, pos[1]+pos[2]))]
|
||||
endif
|
||||
endfor
|
||||
return id_list
|
||||
endfunction
|
||||
endif
|
||||
"}}}
|
||||
function! s:matchdelete_all(ids) abort "{{{
|
||||
if empty(a:ids)
|
||||
return
|
||||
endif
|
||||
|
||||
let alive_ids = map(getmatches(), 'v:val.id')
|
||||
" Return if another plugin called clearmatches() which clears *ALL*
|
||||
" highlights including others set.
|
||||
if empty(alive_ids)
|
||||
return
|
||||
endif
|
||||
if !count(alive_ids, a:ids[0])
|
||||
return
|
||||
endif
|
||||
|
||||
for id in a:ids
|
||||
try
|
||||
call matchdelete(id)
|
||||
catch
|
||||
endtry
|
||||
endfor
|
||||
call filter(a:ids, 0)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_ahead(pos1, pos2) abort "{{{
|
||||
return a:pos1[1] > a:pos2[1] || (a:pos1[1] == a:pos2[1] && a:pos1[2] > a:pos2[2])
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_equal_or_ahead(pos1, pos2) abort "{{{
|
||||
return a:pos1[1] > a:pos2[1] || (a:pos1[1] == a:pos2[1] && a:pos1[2] >= a:pos2[2])
|
||||
endfunction
|
||||
"}}}
|
||||
" function! s:is_in_cmdline_window() abort "{{{
|
||||
if s:has_patch_7_4_392
|
||||
function! s:is_in_cmdline_window() abort
|
||||
return getcmdwintype() !=# ''
|
||||
endfunction
|
||||
else
|
||||
function! s:is_in_cmdline_window() abort
|
||||
let is_in_cmdline_window = 0
|
||||
try
|
||||
execute 'tabnext ' . tabpagenr()
|
||||
catch /^Vim\%((\a\+)\)\=:E11/
|
||||
let is_in_cmdline_window = 1
|
||||
catch
|
||||
finally
|
||||
return is_in_cmdline_window
|
||||
endtry
|
||||
endfunction
|
||||
endif
|
||||
"}}}
|
||||
" function! s:is_in_popup_terminal_window() abort "{{{
|
||||
if exists('*popup_list')
|
||||
function! s:is_in_popup_terminal_window() abort
|
||||
return &buftype is# 'terminal' && count(popup_list(), win_getid())
|
||||
endfunction
|
||||
else
|
||||
function! s:is_in_popup_terminal_window() abort
|
||||
return 0
|
||||
endfunction
|
||||
endif
|
||||
" }}}
|
||||
function! s:shift_options() abort "{{{
|
||||
let options = {}
|
||||
|
||||
""" tweak appearance
|
||||
" hide_cursor
|
||||
if s:has_gui_running
|
||||
let options.cursor = &guicursor
|
||||
set guicursor+=a:block-NONE
|
||||
else
|
||||
let options.cursor = &t_ve
|
||||
set t_ve=
|
||||
endif
|
||||
|
||||
return options
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:restore_options(options) abort "{{{
|
||||
if s:has_gui_running
|
||||
set guicursor&
|
||||
let &guicursor = a:options.cursor
|
||||
else
|
||||
let &t_ve = a:options.cursor
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:search_highlighted_windows(id, ...) abort "{{{
|
||||
if a:id == []
|
||||
return 0
|
||||
endif
|
||||
|
||||
let original_winnr = winnr()
|
||||
let original_tabnr = tabpagenr()
|
||||
let original_view = winsaveview()
|
||||
let tablist = range(1, tabpagenr('$'))
|
||||
if a:0 > 0
|
||||
let tabnr = a:1
|
||||
let [tabnr, winnr] = s:scan_windows(a:id, tabnr)
|
||||
if tabnr != 0
|
||||
return [tabnr, winnr]
|
||||
endif
|
||||
call filter(tablist, 'v:val != tabnr')
|
||||
endif
|
||||
|
||||
for tabnr in tablist
|
||||
let [tabnr, winnr] = s:scan_windows(a:id, tabnr)
|
||||
if tabnr != 0
|
||||
return [tabnr, winnr]
|
||||
endif
|
||||
endfor
|
||||
call s:goto_window(original_winnr, original_tabnr, original_view)
|
||||
return [0, 0]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:scan_windows(id, tabnr) abort "{{{
|
||||
if s:goto_tab(a:tabnr)
|
||||
for winnr in range(1, winnr('$'))
|
||||
if s:goto_window(winnr) && s:is_highlighted_window(a:id)
|
||||
return [a:tabnr, winnr]
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
return [0, 0]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_highlighted_window(id) abort "{{{
|
||||
if a:id != []
|
||||
let id = a:id[0]
|
||||
if filter(getmatches(), 'v:val.id == id') != []
|
||||
return 1
|
||||
endif
|
||||
endif
|
||||
return 0
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:goto_window(winnr, ...) abort "{{{
|
||||
if a:0 > 0
|
||||
if !s:goto_tab(a:1)
|
||||
return 0
|
||||
endif
|
||||
endif
|
||||
|
||||
|
||||
try
|
||||
if a:winnr != winnr()
|
||||
execute printf('noautocmd %swincmd w', a:winnr)
|
||||
endif
|
||||
catch /^Vim\%((\a\+)\)\=:E16/
|
||||
return 0
|
||||
endtry
|
||||
|
||||
if a:0 > 1
|
||||
noautocmd call winrestview(a:2)
|
||||
endif
|
||||
|
||||
return 1
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:goto_tab(tabnr) abort "{{{
|
||||
if a:tabnr != tabpagenr()
|
||||
execute 'noautocmd tabnext ' . a:tabnr
|
||||
endif
|
||||
return tabpagenr() == a:tabnr ? 1 : 0
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,438 @@
|
||||
let s:save_cpo = &cpo
|
||||
set cpo&vim
|
||||
|
||||
" variables "{{{
|
||||
let s:FALSE = 0
|
||||
let s:TRUE = 1
|
||||
let s:null_pos = [0, 0]
|
||||
|
||||
" patchs
|
||||
if v:version > 704 || (v:version == 704 && has('patch237'))
|
||||
let s:has_patch_7_4_1685 = has('patch-7.4.1685')
|
||||
let s:has_patch_7_4_2011 = has('patch-7.4.2011')
|
||||
else
|
||||
let s:has_patch_7_4_1685 = v:version == 704 && has('patch1685')
|
||||
let s:has_patch_7_4_2011 = v:version == 704 && has('patch2011')
|
||||
endif
|
||||
"}}}
|
||||
|
||||
" default patterns
|
||||
let g:sandwich#magicchar#f#default_patterns = [
|
||||
\ {
|
||||
\ 'header' : '\<\h\k*',
|
||||
\ 'bra' : '(',
|
||||
\ 'ket' : ')',
|
||||
\ 'footer' : '',
|
||||
\ },
|
||||
\ ]
|
||||
|
||||
function! sandwich#magicchar#f#fname() abort "{{{
|
||||
call operator#sandwich#show()
|
||||
try
|
||||
echohl MoreMsg
|
||||
if &filetype ==# 'vim'
|
||||
let funcname = input('funcname: ', '', 'custom,sandwich#magicchar#f#fnamecompl_vim')
|
||||
else
|
||||
let funcname = input('funcname: ', '', 'custom,sandwich#magicchar#f#fnamecompl')
|
||||
endif
|
||||
" flash prompt
|
||||
echo ''
|
||||
finally
|
||||
echohl NONE
|
||||
call operator#sandwich#quench()
|
||||
endtry
|
||||
if funcname ==# ''
|
||||
throw 'OperatorSandwichCancel'
|
||||
endif
|
||||
return funcname . '('
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#f#fnamecompl(ArgLead, CmdLine, CursorPos) abort "{{{
|
||||
return join(uniq(sort(s:buffer_completion())), "\n")
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#f#fnamecompl_vim(ArgLead, CmdLine, CursorPos) abort "{{{
|
||||
if s:has_patch_7_4_2011
|
||||
let getcomp = map(filter(getcompletion(a:ArgLead, 'function'), 'v:val =~# ''\C^[a-z][a-zA-Z0-9_]*($'''), 'matchstr(v:val, ''\C^[a-z][a-zA-Z0-9_]*'')')
|
||||
else
|
||||
let getcomp = []
|
||||
endif
|
||||
let buffer = s:buffer_completion()
|
||||
return join(uniq(sort(getcomp + buffer)), "\n")
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:buffer_completion() abort "{{{
|
||||
" NOTE: This func does neither sort nor uniq.
|
||||
let list = []
|
||||
let lines = getline(1, '$')
|
||||
let pattern_list = s:resolve_patterns()
|
||||
for func in pattern_list
|
||||
let pat = printf('%s\ze%s', func.header, func.bra)
|
||||
for line in lines
|
||||
let list += s:extract_pattern(line, pat)
|
||||
endfor
|
||||
endfor
|
||||
return list
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:extract_pattern(string, pat) abort "{{{
|
||||
let list = []
|
||||
let end = 0
|
||||
while 1
|
||||
let [str, start, end] = s:matchstrpos(a:string, a:pat, end)
|
||||
if start < 0
|
||||
break
|
||||
endif
|
||||
let list += [str]
|
||||
endwhile
|
||||
return list
|
||||
endfunction
|
||||
"}}}
|
||||
" function! s:matchstrpos(expr, pat, ...) abort "{{{
|
||||
if s:has_patch_7_4_1685
|
||||
let s:matchstrpos = function('matchstrpos')
|
||||
else
|
||||
function! s:matchstrpos(expr, pat, ...) abort
|
||||
if a:0 == 0
|
||||
return [matchstr(a:expr, a:pat), match(a:expr, a:pat), matchend(a:expr, a:pat)]
|
||||
elseif a:0 == 1
|
||||
return [matchstr(a:expr, a:pat, a:1), match(a:expr, a:pat, a:1), matchend(a:expr, a:pat, a:1)]
|
||||
else
|
||||
return [matchstr(a:expr, a:pat, a:1, a:2), match(a:expr, a:pat, a:1, a:2), matchend(a:expr, a:pat, a:1, a:2)]
|
||||
endif
|
||||
endfunction
|
||||
endif
|
||||
"}}}
|
||||
|
||||
|
||||
|
||||
" textobj-functioncall (bundle version)
|
||||
" NOTE: https://github.com/machakann/vim-textobj-functioncall
|
||||
|
||||
function! sandwich#magicchar#f#i(mode) abort "{{{
|
||||
call sandwich#magicchar#f#textobj('i', a:mode)
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#f#a(mode) abort "{{{
|
||||
call sandwich#magicchar#f#textobj('a', a:mode)
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#f#ip(mode) abort "{{{
|
||||
call sandwich#magicchar#f#textobj('ip', a:mode)
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#f#ap(mode) abort "{{{
|
||||
call sandwich#magicchar#f#textobj('ap', a:mode)
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#f#textobj(kind, mode, ...) abort "{{{
|
||||
let l:count = v:count1
|
||||
if a:0
|
||||
let pattern_list = a:1
|
||||
else
|
||||
let pattern_list = s:resolve_patterns()
|
||||
endif
|
||||
let searchlines = s:get('textobj_sandwich_function_searchlines' , 30)
|
||||
let stopline = {}
|
||||
if searchlines < 0
|
||||
let stopline.upper = 1
|
||||
let stopline.lower = line('$')
|
||||
else
|
||||
let stopline.upper = max([1, line('.') - searchlines])
|
||||
let stopline.lower = min([line('.') + searchlines, line('$')])
|
||||
endif
|
||||
|
||||
let [start, end] = [s:null_pos, s:null_pos]
|
||||
let view = winsaveview()
|
||||
try
|
||||
let candidates = s:gather_candidates(a:kind, a:mode, l:count, pattern_list, stopline)
|
||||
let elected = s:elect(candidates, l:count)
|
||||
if elected != {}
|
||||
let [start, end] = s:to_range(a:kind, elected)
|
||||
endif
|
||||
finally
|
||||
call winrestview(view)
|
||||
endtry
|
||||
call s:select(start, end)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
|
||||
function! s:Candidate(head, bra, ket, tail, pat, rank) abort
|
||||
return {
|
||||
\ 'head': a:head,
|
||||
\ 'bra': a:bra,
|
||||
\ 'ket': a:ket,
|
||||
\ 'tail': a:tail,
|
||||
\ 'rank': a:rank,
|
||||
\ 'pattern': a:pat,
|
||||
\ 'len': s:buflen(a:head, a:tail),
|
||||
\ }
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:gather_candidates(kind, mode, count, pattern_list, stopline) abort "{{{
|
||||
let curpos = getpos('.')[1:2]
|
||||
let rank = 0
|
||||
let candidates = []
|
||||
for pattern in a:pattern_list
|
||||
let rank += 1
|
||||
let candidates += s:search_pattern(pattern, a:kind, a:mode, a:count, rank, curpos, a:stopline)
|
||||
call cursor(curpos)
|
||||
endfor
|
||||
return a:kind[0] is# 'a' && candidates == []
|
||||
\ ? s:gather_candidates('i', a:mode, a:count, a:pattern_list, a:stopline)
|
||||
\ : candidates
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:search_pattern(pat, kind, mode, count, rank, curpos, stopline) abort "{{{
|
||||
let a:pat.head = a:pat.header . a:pat.bra
|
||||
let a:pat.tail = a:pat.ket . a:pat.footer
|
||||
|
||||
let brapos = s:search_key_bra(a:kind, a:curpos, a:pat, a:stopline)
|
||||
if brapos == s:null_pos | return [] | endif
|
||||
let is_string = s:is_string_syntax(brapos)
|
||||
|
||||
let candidates = []
|
||||
while len(candidates) < a:count
|
||||
let c = s:get_candidate(a:pat, a:kind, a:mode, a:rank, brapos, is_string, a:stopline)
|
||||
if c != {}
|
||||
call add(candidates, c)
|
||||
endif
|
||||
call cursor(brapos)
|
||||
|
||||
" move to the next 'bra'
|
||||
let brapos = searchpairpos(a:pat.bra, '', a:pat.ket, 'b', '', a:stopline.upper)
|
||||
if brapos == s:null_pos | break | endif
|
||||
let is_string = s:is_string_syntax(brapos)
|
||||
endwhile
|
||||
return candidates
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:search_key_bra(kind, curpos, pat, stopline) abort "{{{
|
||||
let brapos = s:null_pos
|
||||
if a:kind[0] is# 'a'
|
||||
" search for the first 'bra'
|
||||
if searchpos(a:pat.tail, 'cn', a:stopline.lower) == a:curpos
|
||||
let brapos = searchpairpos(a:pat.bra, '', a:pat.ket, 'b', '', a:stopline.upper)
|
||||
endif
|
||||
let brapos = searchpairpos(a:pat.bra, '', a:pat.ket, 'b', '', a:stopline.upper)
|
||||
elseif a:kind[0] is# 'i'
|
||||
let head_start = searchpos(a:pat.head, 'bc', a:stopline.upper)
|
||||
let head_end = searchpos(a:pat.head, 'ce', a:stopline.lower)
|
||||
call cursor(a:curpos)
|
||||
let tail_start = searchpos(a:pat.tail, 'bc', a:stopline.upper)
|
||||
let tail_end = searchpos(a:pat.tail, 'ce', a:stopline.lower)
|
||||
|
||||
" check the initial position
|
||||
if s:is_in_between(a:curpos, head_start, head_end)
|
||||
" cursor is on a header
|
||||
call cursor(head_end)
|
||||
elseif s:is_in_between(a:curpos, tail_start, tail_end)
|
||||
" cursor is on a footer
|
||||
call cursor(tail_start)
|
||||
if tail_start[1] == 1
|
||||
normal! k$
|
||||
else
|
||||
normal! h
|
||||
endif
|
||||
else
|
||||
" cursor is in between a bra and a ket
|
||||
call cursor(a:curpos)
|
||||
endif
|
||||
|
||||
" move to the corresponded 'bra'
|
||||
let brapos = searchpairpos(a:pat.bra, '', a:pat.ket, 'bc', '', a:stopline.upper)
|
||||
endif
|
||||
return brapos
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_candidate(pat, kind, mode, rank, brapos, is_string, stopline) abort "{{{
|
||||
" 'bra' should accompany with 'header'
|
||||
let headstart = searchpos(a:pat.head, 'bc', a:stopline.upper)
|
||||
let headend = searchpos(a:pat.head, 'ce', a:stopline.lower)
|
||||
call cursor(a:brapos)
|
||||
if !s:is_in_between(a:brapos, headstart, headend)
|
||||
return {}
|
||||
endif
|
||||
let headpos = headstart
|
||||
|
||||
" search for the paired 'ket'
|
||||
let skip = 's:is_string_syntax(getpos(".")[1:2]) != a:is_string'
|
||||
let ketpos = searchpairpos(a:pat.bra, '', a:pat.ket, '', skip, a:stopline.lower)
|
||||
if ketpos == s:null_pos
|
||||
return {}
|
||||
endif
|
||||
let tailpos = searchpos(a:pat.tail, 'ce', a:stopline.lower)
|
||||
|
||||
if searchpos(a:pat.tail, 'bcn', a:stopline.upper) != ketpos
|
||||
return {}
|
||||
endif
|
||||
|
||||
let c = s:Candidate(headpos, a:brapos, ketpos, tailpos, a:pat, a:rank)
|
||||
if !s:is_valid_candidate(c, a:kind, a:mode, a:is_string)
|
||||
return {}
|
||||
endif
|
||||
return c
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:elect(candidates, count) abort "{{{
|
||||
if a:candidates == []
|
||||
return {}
|
||||
endif
|
||||
let filter = 'v:val.head != s:null_pos && v:val.bra != s:null_pos && v:val.ket != s:null_pos && v:val.tail != s:null_pos'
|
||||
call filter(a:candidates, filter)
|
||||
call sort(a:candidates, 's:compare')
|
||||
if len(a:candidates) < a:count
|
||||
return {}
|
||||
endif
|
||||
return a:candidates[a:count - 1]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:to_range(kind, candidate) abort "{{{
|
||||
if a:kind[1] is# 'p'
|
||||
let [start, end] = s:parameter_region(a:candidate)
|
||||
else
|
||||
let start = a:candidate.head
|
||||
let end = a:candidate.tail
|
||||
endif
|
||||
return [start, end]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:select(start, end) abort "{{{
|
||||
if a:start == s:null_pos || a:end == s:null_pos
|
||||
return
|
||||
endif
|
||||
normal! v
|
||||
call cursor(a:start)
|
||||
normal! o
|
||||
call cursor(a:end)
|
||||
if &selection is# 'exclusive'
|
||||
normal! l
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_in_between(pos, start, end) abort "{{{
|
||||
return (a:pos != s:null_pos) && (a:start != s:null_pos) && (a:end != s:null_pos)
|
||||
\ && ((a:pos[0] > a:start[0]) || ((a:pos[0] == a:start[0]) && (a:pos[1] >= a:start[1])))
|
||||
\ && ((a:pos[0] < a:end[0]) || ((a:pos[0] == a:end[0]) && (a:pos[1] <= a:end[1])))
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_string_syntax(pos) abort "{{{
|
||||
return match(map(synstack(a:pos[0], a:pos[1]), 'synIDattr(synIDtrans(v:val), "name")'), 'String') > -1
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_continuous_syntax(brapos, ketpos) abort "{{{
|
||||
let start_col = a:brapos[1]
|
||||
for lnum in range(a:brapos[0], a:ketpos[0])
|
||||
if lnum == a:ketpos[0]
|
||||
let end_col= a:ketpos[1]
|
||||
else
|
||||
let end_col= col([lnum, '$'])
|
||||
endif
|
||||
for col in range(start_col, end_col)
|
||||
if match(map(synstack(lnum, col), 'synIDattr(synIDtrans(v:val), "name")'), 'String') < 0
|
||||
return 0
|
||||
endif
|
||||
endfor
|
||||
let start_col = 1
|
||||
endfor
|
||||
return 1
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_valid_syntax(candidate, is_string) abort "{{{
|
||||
return !a:is_string || s:is_continuous_syntax(a:candidate.bra, a:candidate.ket)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_same_or_adjacent(p1, p2) abort "{{{
|
||||
return a:p1 == a:p2 || (a:p1[0] == a:p2[0] && a:p1[1]+1 == a:p2[1])
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_wider(candidate, start, end) abort "{{{
|
||||
return (s:is_ahead(a:start, a:candidate.head) && s:is_same_or_ahead(a:candidate.tail, a:end)) ||
|
||||
\ (s:is_same_or_ahead(a:start, a:candidate.head) && s:is_ahead(a:candidate.tail, a:end))
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_ahead(p1, p2) abort "{{{
|
||||
return (a:p1[0] > a:p2[0]) || (a:p1[0] == a:p2[0] && a:p1[1] > a:p2[1])
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_same_or_ahead(p1, p2) abort "{{{
|
||||
return (a:p1[0] > a:p2[0]) || (a:p1[0] == a:p2[0] && a:p1[1] >= a:p2[1])
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_valid_candidate(c, kind, mode, is_string) abort "{{{
|
||||
if a:kind[1] is# 'p' && s:is_same_or_adjacent(a:c.bra, a:c.ket)
|
||||
return s:FALSE
|
||||
endif
|
||||
if a:mode is# 'x' && !s:is_wider(a:c, getpos("'<")[1:2], getpos("'>")[1:2])
|
||||
return s:FALSE
|
||||
endif
|
||||
if !s:is_valid_syntax(a:c, a:is_string)
|
||||
return s:FALSE
|
||||
endif
|
||||
return s:TRUE
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:resolve_patterns() abort "{{{
|
||||
return deepcopy(get(b:, 'sandwich_magicchar_f_patterns',
|
||||
\ get(g:, 'sandwich#magicchar#f#patterns',
|
||||
\ g:sandwich#magicchar#f#default_patterns)))
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:parameter_region(candidate) abort "{{{
|
||||
let whichwrap = &whichwrap
|
||||
let &whichwrap = 'h,l'
|
||||
let [visualhead, visualtail] = [getpos("'<"), getpos("'>")]
|
||||
try
|
||||
normal! v
|
||||
call cursor(a:candidate.bra)
|
||||
call search(a:candidate.pattern.bra, 'ce', a:candidate.ket[0])
|
||||
normal! l
|
||||
let head = getpos('.')[1:2]
|
||||
normal! o
|
||||
call cursor(a:candidate.ket)
|
||||
normal! h
|
||||
let tail = getpos('.')[1:2]
|
||||
execute "normal! \<Esc>"
|
||||
finally
|
||||
let &whichwrap = whichwrap
|
||||
call setpos("'<", visualhead)
|
||||
call setpos("'>", visualtail)
|
||||
endtry
|
||||
return [head, tail]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:buflen(start, end) abort "{{{
|
||||
" start, end -> [lnum, col]
|
||||
if a:start[0] == a:end[0]
|
||||
let len = a:end[1] - a:start[1] + 1
|
||||
else
|
||||
let len = (line2byte(a:end[0]) + a:end[1]) - (line2byte(a:start[0]) + a:start[1]) + 1
|
||||
endif
|
||||
return len
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:compare(i1, i2) abort "{{{
|
||||
" i1, i2 -> Candidate
|
||||
if a:i1.len < a:i2.len
|
||||
return -1
|
||||
elseif a:i1.len > a:i2.len
|
||||
return 1
|
||||
else
|
||||
return a:i2.rank - a:i1.rank
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get(name, default) abort "{{{
|
||||
return exists('b:' . a:name) ? b:[a:name]
|
||||
\ : exists('g:' . a:name) ? g:[a:name]
|
||||
\ : a:default
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
let &cpo = s:save_cpo
|
||||
unlet s:save_cpo
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
@ -0,0 +1,102 @@
|
||||
let s:last_inserted = []
|
||||
let s:last_searched = []
|
||||
|
||||
" variables "{{{
|
||||
" patchs
|
||||
if v:version > 704 || (v:version == 704 && has('patch237'))
|
||||
let s:has_patch_7_4_1685 = has('patch-7.4.1685')
|
||||
else
|
||||
let s:has_patch_7_4_1685 = v:version == 704 && has('patch1685')
|
||||
endif
|
||||
"}}}
|
||||
|
||||
function! sandwich#magicchar#i#input(kind, ...) abort "{{{
|
||||
let storepoint = a:kind ==# 'operator' ? 'last_inserted' : 'last_searched'
|
||||
let former = s:input(a:kind . '-sandwich:head: ')
|
||||
let quit_if_empty = get(a:000, 0, 0)
|
||||
if quit_if_empty && former ==# ''
|
||||
let s:{storepoint} = []
|
||||
return ['', '']
|
||||
endif
|
||||
let latter = s:input(a:kind . '-sandwich:tail: ')
|
||||
let s:{storepoint} = [former, latter]
|
||||
return copy(s:{storepoint})
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#i#lastinput(kind, ...) abort "{{{
|
||||
let storepoint = a:kind ==# 'operator' ? 'last_inserted' : 'last_searched'
|
||||
let cancel_if_invalid = get(a:000, 0, 0)
|
||||
if cancel_if_invalid && s:{storepoint} == []
|
||||
throw 'OperatorSandwichCancel'
|
||||
endif
|
||||
return s:{storepoint} != [] ? copy(s:{storepoint}) : ['', '']
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#i#compl(ArgLead, CmdLine, CursorPos) abort "{{{
|
||||
let list = []
|
||||
let lines = getline(1, '$')
|
||||
if a:ArgLead ==# ''
|
||||
let list += s:extract_patterns_from_lines(lines, '\<\k\{3,}\>')
|
||||
else
|
||||
let list += s:extract_patterns_from_lines(lines, printf('\<%s.\{-}\>', a:ArgLead))
|
||||
if list == []
|
||||
let tail = matchstr(a:ArgLead, '\<\k\+$')
|
||||
if tail !=# '' && tail !=# a:ArgLead
|
||||
let list += s:extract_patterns_from_lines(lines, tail . '.\{-}\>')
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
return join(uniq(sort(list)), "\n")
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:input(mes) abort "{{{
|
||||
echohl MoreMsg
|
||||
try
|
||||
let input = input(a:mes, '', 'custom,sandwich#magicchar#i#compl')
|
||||
" flash prompt
|
||||
echo ''
|
||||
finally
|
||||
echohl NONE
|
||||
endtry
|
||||
return input
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:extract_patterns_from_lines(lines, pat) abort "{{{
|
||||
let list = []
|
||||
for line in a:lines
|
||||
let list += s:extract_pattern(line, a:pat)
|
||||
endfor
|
||||
return list
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:extract_pattern(string, pat) abort "{{{
|
||||
let list = []
|
||||
let end = 0
|
||||
while 1
|
||||
let [str, start, end] = s:matchstrpos(a:string, a:pat, end)
|
||||
if start < 0
|
||||
break
|
||||
endif
|
||||
let list += [str]
|
||||
endwhile
|
||||
return list
|
||||
endfunction
|
||||
"}}}
|
||||
" function! s:matchstrpos(expr, pat, ...) abort "{{{
|
||||
if s:has_patch_7_4_1685
|
||||
let s:matchstrpos = function('matchstrpos')
|
||||
else
|
||||
function! s:matchstrpos(expr, pat, ...) abort
|
||||
if a:0 == 0
|
||||
return [matchstr(a:expr, a:pat), match(a:expr, a:pat), matchend(a:expr, a:pat)]
|
||||
elseif a:0 == 1
|
||||
return [matchstr(a:expr, a:pat, a:1), match(a:expr, a:pat, a:1), matchend(a:expr, a:pat, a:1)]
|
||||
else
|
||||
return [matchstr(a:expr, a:pat, a:1, a:2), match(a:expr, a:pat, a:1, a:2), matchend(a:expr, a:pat, a:1, a:2)]
|
||||
endif
|
||||
endfunction
|
||||
endif
|
||||
"}}}
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
@ -0,0 +1,446 @@
|
||||
" variables "{{{
|
||||
let s:null_coord = [0, 0]
|
||||
|
||||
" types
|
||||
let s:type_num = type(0)
|
||||
let s:type_str = type('')
|
||||
let s:type_list = type([])
|
||||
"}}}
|
||||
|
||||
function! sandwich#magicchar#t#tag() abort "{{{
|
||||
call operator#sandwich#show()
|
||||
try
|
||||
echohl MoreMsg
|
||||
let old_imsearch = &l:imsearch
|
||||
let &l:imsearch = 0
|
||||
let tag = input('Input tag: ')
|
||||
let &l:imsearch = old_imsearch
|
||||
" flash prompt
|
||||
echo ''
|
||||
finally
|
||||
echohl NONE
|
||||
call operator#sandwich#quench()
|
||||
endtry
|
||||
if tag ==# ''
|
||||
throw 'OperatorSandwichCancel'
|
||||
endif
|
||||
let [open, close] = s:expand(tag)
|
||||
return [printf('<%s>', open), printf('</%s>', close)]
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#t#tagname() abort "{{{
|
||||
call operator#sandwich#show()
|
||||
try
|
||||
echohl MoreMsg
|
||||
let old_imsearch = &l:imsearch
|
||||
let &l:imsearch = 0
|
||||
let tagname = input('Input tag name: ')
|
||||
let &l:imsearch = old_imsearch
|
||||
" flash prompt
|
||||
echo ''
|
||||
finally
|
||||
echohl NONE
|
||||
call operator#sandwich#quench()
|
||||
endtry
|
||||
if tagname ==# ''
|
||||
throw 'OperatorSandwichCancel'
|
||||
endif
|
||||
return s:expand(tagname)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:tokenize(text) abort "{{{
|
||||
let j = 0
|
||||
let n = strlen(a:text)
|
||||
let tokenlist = []
|
||||
while j >= 0 && j < n
|
||||
let i = j
|
||||
let j = match(a:text, '\m[[[:blank:]#.]', i)
|
||||
if i < j
|
||||
let tokenlist += [a:text[i : j-1]]
|
||||
elseif i == j
|
||||
let char = a:text[i]
|
||||
if char =~# '\m\s'
|
||||
" space is not allowed
|
||||
throw 'SandwichMagiccharTIncorrectSyntax'
|
||||
elseif char ==# '['
|
||||
" tokenize custom attributes
|
||||
let tokenlist += [char]
|
||||
let j += 1
|
||||
let [j, tokenlist] += s:tokenize_custom_attributes(a:text, j)
|
||||
else
|
||||
let tokenlist += [char]
|
||||
let j += 1
|
||||
endif
|
||||
endif
|
||||
endwhile
|
||||
|
||||
let i = j >= 0 ? j : i
|
||||
if i < n
|
||||
let tokenlist += [a:text[i :]]
|
||||
endif
|
||||
return tokenlist
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:tokenize_custom_attributes(text, j) abort "{{{
|
||||
let j = a:j
|
||||
let n = strlen(a:text)
|
||||
let closed = 0
|
||||
let tokenlist = []
|
||||
while j >= 0 && j < n
|
||||
let i = j
|
||||
let j = match(a:text, '\m[][:blank:]="'']', i)
|
||||
if i < j
|
||||
let string = a:text[i : j-1]
|
||||
let tokenlist += [string]
|
||||
elseif i == j
|
||||
let char = a:text[i]
|
||||
if char =~# '\m\s'
|
||||
" skip space
|
||||
let j = matchend(a:text, '\m\s\+', i)
|
||||
if j > i
|
||||
let tokenlist += [a:text[i : j-1]]
|
||||
endif
|
||||
elseif char =~# '\m["'']'
|
||||
" skip string literal
|
||||
let j = match(a:text, char, i+1)
|
||||
if j > 0
|
||||
let tokenlist += [a:text[i : j]]
|
||||
let j += 1
|
||||
else
|
||||
" unclosed string literal
|
||||
throw 'SandwichMagiccharTIncorrectSyntax'
|
||||
endif
|
||||
elseif char ==# ']'
|
||||
" the end of custom attributes region
|
||||
let tokenlist += [char]
|
||||
let j += 1
|
||||
let closed = 1
|
||||
break
|
||||
else
|
||||
let tokenlist += [char]
|
||||
let j += 1
|
||||
endif
|
||||
endif
|
||||
endwhile
|
||||
if !closed
|
||||
" unclosed braket
|
||||
throw 'SandwichMagiccharTIncorrectSyntax'
|
||||
endif
|
||||
return [j - a:j, tokenlist]
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
|
||||
function! s:matches_filetype(filetype, list) abort "{{{
|
||||
return index(a:list, a:filetype) != -1
|
||||
endfunction
|
||||
" }}}
|
||||
|
||||
function! s:parse(tokenlist) abort "{{{
|
||||
let itemdict = deepcopy(s:itemdict)
|
||||
let itemlist = map(copy(a:tokenlist), '{"is_operator": v:val =~# ''^\%([][#.=]\|\s\+\)$'' ? 1 : 0, "string": v:val}')
|
||||
|
||||
let i = 0
|
||||
let n = len(itemlist)
|
||||
let item = itemlist[i]
|
||||
if item.is_operator
|
||||
call itemdict.queue_element('div')
|
||||
else
|
||||
call itemdict.queue_element(item.string)
|
||||
let i += 1
|
||||
endif
|
||||
while i < n
|
||||
let item = itemlist[i]
|
||||
if item.is_operator
|
||||
if item.string ==# '#'
|
||||
let i = s:handle_id(itemdict, itemlist, i)
|
||||
elseif item.string ==# '.'
|
||||
if s:matches_filetype(&filetype, g:sandwich#jsx_filetypes)
|
||||
let i = s:handle_className(itemdict, itemlist, i)
|
||||
else
|
||||
let i = s:handle_class(itemdict, itemlist, i)
|
||||
endif
|
||||
elseif item.string ==# '['
|
||||
let i = s:parse_custom_attributes(itemdict, itemlist, i)
|
||||
else
|
||||
" unanticipated operator (should not reach here)
|
||||
throw 'SandwichMagiccharTIncorrectSyntax'
|
||||
endif
|
||||
else
|
||||
" successive operand is not allowed here (should not reach here)
|
||||
throw 'SandwichMagiccharTIncorrectSyntax'
|
||||
endif
|
||||
endwhile
|
||||
|
||||
let parsed = deepcopy(itemdict.queue)
|
||||
for item in parsed
|
||||
call filter(item, 'v:key =~# ''\%(name\|value\)''')
|
||||
endfor
|
||||
return parsed
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:parse_custom_attributes(itemdict, itemlist, i) abort "{{{
|
||||
" check ket
|
||||
let i_ket = s:check_ket(a:itemlist, a:i)
|
||||
if i_ket < 0
|
||||
" ket does not exist (should not reach here)
|
||||
throw 'SandwichMagiccharTIncorrectSyntax'
|
||||
endif
|
||||
|
||||
let i = a:i + 1
|
||||
while i < i_ket
|
||||
let item = a:itemlist[i]
|
||||
if item.is_operator
|
||||
if item.string ==# '='
|
||||
let i = s:handle_equal(a:itemdict, a:itemlist, i)
|
||||
else
|
||||
let i += 1
|
||||
endif
|
||||
else
|
||||
let i = s:handle_custom_attr_name(a:itemdict, a:itemlist, i)
|
||||
endif
|
||||
endwhile
|
||||
call a:itemdict.queue_custom_attr()
|
||||
return i_ket + 1
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:handle_id(itemdict, itemlist, i) abort "{{{
|
||||
let i = a:i + 1
|
||||
let item = get(a:itemlist, i, {'is_operator': 1})
|
||||
if item.is_operator
|
||||
call a:itemdict.queue_id()
|
||||
else
|
||||
call a:itemdict.queue_id(item.string)
|
||||
let i += 1
|
||||
endif
|
||||
return i
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:handle_class(itemdict, itemlist, i) abort "{{{
|
||||
let i = a:i + 1
|
||||
let item = get(a:itemlist, i, {'is_operator': 1})
|
||||
if item.is_operator
|
||||
call a:itemdict.queue_class()
|
||||
else
|
||||
call a:itemdict.queue_class(item.string)
|
||||
let i += 1
|
||||
endif
|
||||
return i
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:handle_className(itemdict, itemlist, i) abort "{{{
|
||||
let i = a:i + 1
|
||||
let item = get(a:itemlist, i, {'is_operator': 1})
|
||||
if item.is_operator
|
||||
call a:itemdict.queue_className()
|
||||
else
|
||||
call a:itemdict.queue_className(item.string)
|
||||
let i += 1
|
||||
endif
|
||||
return i
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:handle_equal(itemdict, itemlist, i, ...) abort "{{{
|
||||
let i = a:i + 1
|
||||
let item = a:itemlist[i]
|
||||
let name = get(a:000, 0, '')
|
||||
if item.is_operator
|
||||
if item.string ==# '='
|
||||
call a:itemdict.list_custom_attr(name)
|
||||
else
|
||||
call a:itemdict.list_custom_attr(name)
|
||||
let i += 1
|
||||
endif
|
||||
else
|
||||
call a:itemdict.list_custom_attr(name, item.string)
|
||||
let i += 1
|
||||
endif
|
||||
return i
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:handle_custom_attr_name(itemdict, itemlist, i) abort "{{{
|
||||
let item = a:itemlist[a:i]
|
||||
let name = item.string
|
||||
let i = a:i + 1
|
||||
let item = a:itemlist[i]
|
||||
if item.is_operator
|
||||
if item.string ==# '='
|
||||
let i = s:handle_equal(a:itemdict, a:itemlist, i, name)
|
||||
else
|
||||
call a:itemdict.list_custom_attr(name)
|
||||
let i += 1
|
||||
endif
|
||||
else
|
||||
call a:itemdict.list_custom_attr(name)
|
||||
endif
|
||||
return i
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:check_ket(itemlist, i) abort "{{{
|
||||
let i = a:i + 1
|
||||
let n = len(a:itemlist)
|
||||
let i_ket = -1
|
||||
while i < n
|
||||
let item = a:itemlist[i]
|
||||
if item.is_operator && item.string ==# ']'
|
||||
let i_ket = i
|
||||
break
|
||||
endif
|
||||
let i += 1
|
||||
endwhile
|
||||
return i_ket
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:build(itemlist) abort "{{{
|
||||
let fragments = [a:itemlist[0]['value']]
|
||||
if len(a:itemlist) > 1
|
||||
for item in a:itemlist[1:]
|
||||
let name = item.name
|
||||
let value = type(item.value) == s:type_list ? join(filter(item.value, 'v:val !=# ""')) : item.value
|
||||
let value = value !~# '^\(["'']\).*\1$' ? printf('"%s"', value) : value
|
||||
let fragments += [printf('%s=%s', name, value)]
|
||||
endfor
|
||||
endif
|
||||
return join(fragments)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:expand(text) abort "{{{
|
||||
try
|
||||
let tokenlist = s:tokenize(a:text)
|
||||
let itemlist = s:parse(tokenlist)
|
||||
catch /^SandwichMagiccharTIncorrectSyntax$/
|
||||
return [a:text, matchstr(a:text, '^\s*\zs\a[^[:blank:]>/]*')]
|
||||
endtry
|
||||
let element = itemlist[0]['value']
|
||||
return [s:build(itemlist), element]
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
function! sandwich#magicchar#t#i() abort "{{{
|
||||
call s:prototype('i')
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#t#a() abort "{{{
|
||||
call s:prototype('a')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:prototype(kind) abort "{{{
|
||||
let view = winsaveview()
|
||||
let visualhead = getpos("'<")
|
||||
let visualtail = getpos("'>")
|
||||
execute printf('silent! normal! v%dat', v:count1)
|
||||
execute "normal! \<Esc>"
|
||||
if getpos("'<") != getpos("'>")
|
||||
normal! gv
|
||||
let pat = a:kind ==# 'i' ? '</\ze\a[^[:blank:]>/]*' : '</\a[^[:blank:]>/]*\ze\s*>'
|
||||
call search(pat, 'be', line("'<"))
|
||||
normal! o
|
||||
let pat = a:kind ==# 'i' ? '<\a[^[:blank:]>/]*\zs\_.' : '<\zs\a[^[:blank:]>/]*'
|
||||
call search(pat, '', line("'>"))
|
||||
else
|
||||
call winrestview(view)
|
||||
call setpos("'<", visualhead)
|
||||
call setpos("'>", visualtail)
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#t#it() abort "{{{
|
||||
call s:textobj('i')
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#t#at() abort "{{{
|
||||
call s:textobj('a')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:textobj(a_or_i) abort "{{{
|
||||
let head = searchpos('<\a[^[:blank:]>/]*', 'bcn', 0, 50)
|
||||
if head != s:null_coord
|
||||
let tagname = matchstr(getline(head[0])[head[1]-1 :], '^<\zs\a[^[:blank:]>/]*')
|
||||
if search(printf('</%s>', s:escape(tagname)), 'cn', 0, 50)
|
||||
" add :silent! to suppress errorbell
|
||||
if a:a_or_i ==# 'i'
|
||||
silent! normal! vit
|
||||
else
|
||||
silent! normal! vat
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:escape(string) abort "{{{
|
||||
return escape(a:string, '~"\.^$[]*')
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" itemdict object "{{{
|
||||
let s:itemdict = {
|
||||
\ 'element': {'name': 'element', 'value': ''},
|
||||
\ 'id' : {'name': 'id', 'value': '', 'queued': 0},
|
||||
\ 'class' : {'name': 'class', 'value': [], 'queued': 0},
|
||||
\ 'className' : {'name': 'className', 'value': [], 'queued': 0},
|
||||
\ 'queue' : [],
|
||||
\ 'customlist': [],
|
||||
\ }
|
||||
|
||||
function! s:itemdict.queue_element(value) dict abort "{{{
|
||||
let self.element.value = a:value
|
||||
call add(self.queue, self.element)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:itemdict.queue_id(...) dict abort "{{{
|
||||
let self.id.value = get(a:000, 0, '')
|
||||
if !self.id.queued
|
||||
call add(self.queue, self.id)
|
||||
let self.id.queued = 1
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:itemdict.queue_class(...) dict abort "{{{
|
||||
call add(self.class.value, get(a:000, 0, ''))
|
||||
if !self.class.queued
|
||||
call add(self.queue, self.class)
|
||||
let self.class.queued = 1
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:itemdict.queue_className(...) dict abort "{{{
|
||||
call add(self.className.value, get(a:000, 0, ''))
|
||||
if !self.className.queued
|
||||
call add(self.queue, self.className)
|
||||
let self.className.queued = 1
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:itemdict.queue_custom_attr() dict abort "{{{
|
||||
if self.customlist != []
|
||||
call extend(self.queue, remove(self.customlist, 0, -1))
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:itemdict.list_custom_attr(name, ...) dict abort "{{{
|
||||
let value = get(a:000, 0, '')
|
||||
if a:name ==# 'id'
|
||||
call self.queue_id(value)
|
||||
elseif a:name ==# 'class'
|
||||
call self.queue_class(value)
|
||||
endif
|
||||
|
||||
if a:name ==# ''
|
||||
let custom_attr = {'kind': 'custom', 'name': a:name, 'value': value}
|
||||
call add(self.customlist, custom_attr)
|
||||
else
|
||||
if !has_key(self, a:name)
|
||||
let self[a:name] = {'kind': 'custom', 'name': a:name, 'value': value}
|
||||
call add(self.customlist, self[a:name])
|
||||
else
|
||||
let self[a:name]['value'] = value
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
"}}}
|
||||
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
@ -0,0 +1,67 @@
|
||||
" messenger object - managing messages and errors.
|
||||
|
||||
function! sandwich#messenger#new() abort "{{{
|
||||
let s:messenger = deepcopy(s:messenger_prototype)
|
||||
return s:messenger
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#messenger#get() abort "{{{
|
||||
return s:messenger
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" s:messenger_prototype "{{{
|
||||
let s:messenger_prototype = {
|
||||
\ 'error' : {'flag': 0, 'string': ''},
|
||||
\ 'notice': {'flag': 0, 'list': []},
|
||||
\ }
|
||||
"}}}
|
||||
function! s:messenger_prototype.initialize() dict abort "{{{
|
||||
let self.error.flag = 0
|
||||
let self.error.string = ''
|
||||
let self.notice.flag = 0
|
||||
let self.notice.list = []
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:messenger_prototype.notify(prefix) dict abort "{{{
|
||||
if self.error.flag
|
||||
call self.error.echoerr(a:prefix)
|
||||
elseif self.notice.flag
|
||||
call self.notice.echo(a:prefix)
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:messenger_prototype.error.echoerr(prefix) dict abort "{{{
|
||||
echoerr a:prefix . self.string
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:messenger_prototype.notice.echo(prefix) dict abort "{{{
|
||||
let queue = []
|
||||
if self.list != []
|
||||
for line in self.list
|
||||
let queue += [[a:prefix, 'MoreMsg']]
|
||||
let queue += [line]
|
||||
let queue += [["\n", 'NONE']]
|
||||
endfor
|
||||
call remove(queue, -1)
|
||||
endif
|
||||
call sandwich#util#echo(queue)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:messenger_prototype.error.set(errmes) dict abort "{{{
|
||||
let self.flag = 1
|
||||
let self.string = a:errmes
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:messenger_prototype.notice.queue(line) dict abort "{{{
|
||||
let self.flag = 1
|
||||
call add(self.list, a:line)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
call sandwich#messenger#new()
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,346 @@
|
||||
" opt object - managing options
|
||||
|
||||
function! sandwich#opt#new(kind, defaultopt, argopt) abort "{{{
|
||||
let opt = deepcopy(s:opt)
|
||||
if a:kind ==# 'auto' || a:kind ==# 'query'
|
||||
let opt.recipe = {}
|
||||
let opt.of = function('s:of_for_textobj')
|
||||
let opt.filter = s:default_values['textobj']['filter']
|
||||
call opt.update('arg', a:argopt)
|
||||
call opt.update('default', a:defaultopt)
|
||||
else
|
||||
let opt.recipe_add = {}
|
||||
let opt.recipe_delete = {}
|
||||
let opt.of = function('s:of_for_' . a:kind)
|
||||
let opt.filter = s:default_values[a:kind]['filter']
|
||||
call opt.update('arg', a:argopt)
|
||||
endif
|
||||
return opt
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#opt#defaults(kind, ...) abort "{{{
|
||||
if a:kind ==# 'auto' || a:kind ==# 'query'
|
||||
return deepcopy(s:default_values['textobj'][a:kind])
|
||||
elseif a:kind ==# 'add' || a:kind ==# 'delete' || a:kind ==# 'replace'
|
||||
let motionwise = get(a:000, 0, 'char')
|
||||
return deepcopy(s:default_values[a:kind][motionwise])
|
||||
else
|
||||
return {}
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" opt "{{{
|
||||
let s:opt = {
|
||||
\ 'default' : {},
|
||||
\ 'arg' : {},
|
||||
\ 'filter' : '',
|
||||
\ }
|
||||
"}}}
|
||||
function! s:opt.clear(target) dict abort "{{{
|
||||
call filter(self[a:target], 0)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:opt.update(target, dict) dict abort "{{{
|
||||
call self.clear(a:target)
|
||||
call extend(self[a:target], filter(deepcopy(a:dict), self.filter), 'force')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:opt.has(opt_name) dict abort "{{{
|
||||
return has_key(self.default, a:opt_name)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:opt.max(opt_name, ...) dict abort "{{{
|
||||
let minimum = get(a:000, 0, 0)
|
||||
let list = []
|
||||
if has_key(self['recipe_delete'], a:opt_name)
|
||||
let list += [self['recipe_delete'][a:opt_name]]
|
||||
endif
|
||||
if has_key(self['recipe_add'], a:opt_name)
|
||||
let list += [self['recipe_add'][a:opt_name]]
|
||||
endif
|
||||
if list == []
|
||||
let list += [self._of(a:opt_name)]
|
||||
endif
|
||||
return max([minimum] + list)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:opt.get(opt_name, kind, ...) dict abort "{{{
|
||||
return self.has(a:opt_name) ? self.of(a:opt_name, a:kind) : get(a:000, 0, 0)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:opt._of(opt_name, ...) dict abort "{{{
|
||||
let kind = get(a:000, 0, '')
|
||||
if kind !=# '' && has_key(self[kind], a:opt_name)
|
||||
return self[kind][a:opt_name]
|
||||
elseif has_key(self['arg'], a:opt_name)
|
||||
return self['arg'][a:opt_name]
|
||||
else
|
||||
return self['default'][a:opt_name]
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
function! s:of_for_add(opt_name, ...) dict abort "{{{
|
||||
return self._of(a:opt_name, 'recipe_add')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:of_for_delete(opt_name, ...) dict abort "{{{
|
||||
return self._of(a:opt_name, 'recipe_delete')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:of_for_replace(opt_name, ...) dict abort "{{{
|
||||
let kind = get(a:000, 0, '')
|
||||
if kind !=# ''
|
||||
return self._of(a:opt_name, kind)
|
||||
else
|
||||
if a:opt_name ==# 'cursor'
|
||||
" recipe_add > recipe_delete > arg > default
|
||||
if has_key(self.recipe_add, a:opt_name)
|
||||
return self.recipe_add[a:opt_name]
|
||||
else
|
||||
return self._of(a:opt_name, 'recipe_delete')
|
||||
endif
|
||||
elseif a:opt_name ==# 'query_once'
|
||||
return self._of(a:opt_name, 'recipe_add')
|
||||
elseif a:opt_name ==# 'regex'
|
||||
return self._of(a:opt_name, 'recipe_delete')
|
||||
elseif a:opt_name ==# 'expr'
|
||||
return self._of(a:opt_name, 'recipe_add')
|
||||
elseif a:opt_name ==# 'listexpr'
|
||||
return self._of(a:opt_name, 'recipe_add')
|
||||
elseif a:opt_name ==# 'noremap'
|
||||
return self._of(a:opt_name, '')
|
||||
elseif a:opt_name ==# 'skip_space'
|
||||
return self._of(a:opt_name, 'recipe_delete')
|
||||
elseif a:opt_name ==# 'skip_char'
|
||||
return self._of(a:opt_name, 'recipe_delete')
|
||||
elseif a:opt_name ==# 'highlight'
|
||||
return self._of(a:opt_name, '')
|
||||
elseif a:opt_name ==# 'hi_duration'
|
||||
return self._of(a:opt_name, '')
|
||||
elseif a:opt_name ==# 'command'
|
||||
let commands = []
|
||||
let commands += get(self.recipe_delete, a:opt_name, [])
|
||||
let commands += get(self.recipe_add, a:opt_name, [])
|
||||
if commands == []
|
||||
let commands += self._of(a:opt_name)
|
||||
endif
|
||||
return commands
|
||||
elseif a:opt_name ==# 'linewise'
|
||||
return self.max(a:opt_name)
|
||||
elseif a:opt_name ==# 'autoindent'
|
||||
return self._of(a:opt_name, 'recipe_add')
|
||||
elseif a:opt_name ==# 'indentkeys'
|
||||
return self._of(a:opt_name, 'recipe_add')
|
||||
elseif a:opt_name ==# 'indentkeys+'
|
||||
return self._of(a:opt_name, 'recipe_add')
|
||||
elseif a:opt_name ==# 'indentkeys-'
|
||||
return self._of(a:opt_name, 'recipe_add')
|
||||
else
|
||||
" should not reach here!
|
||||
throw 'OperatorSandwichError:Replace:InvalidOption:' . a:opt_name
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:of_for_textobj(opt_name) dict abort "{{{
|
||||
if has_key(self['recipe'], a:opt_name)
|
||||
return self['recipe'][a:opt_name]
|
||||
elseif has_key(self['arg'], a:opt_name)
|
||||
return self['arg'][a:opt_name]
|
||||
else
|
||||
return self['default'][a:opt_name]
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" default values "{{{
|
||||
let s:default_values = {}
|
||||
let s:default_values.add = {}
|
||||
let s:default_values.add.char = {
|
||||
\ 'cursor' : 'default',
|
||||
\ 'query_once' : 0,
|
||||
\ 'expr' : 0,
|
||||
\ 'listexpr' : 0,
|
||||
\ 'noremap' : 1,
|
||||
\ 'skip_space' : 0,
|
||||
\ 'highlight' : 3,
|
||||
\ 'hi_duration': 200,
|
||||
\ 'command' : [],
|
||||
\ 'linewise' : 0,
|
||||
\ 'autoindent' : -1,
|
||||
\ 'indentkeys' : 0,
|
||||
\ 'indentkeys+': 0,
|
||||
\ 'indentkeys-': 0,
|
||||
\ }
|
||||
let s:default_values.add.line = {
|
||||
\ 'cursor' : 'default',
|
||||
\ 'query_once' : 0,
|
||||
\ 'expr' : 0,
|
||||
\ 'listexpr' : 0,
|
||||
\ 'noremap' : 1,
|
||||
\ 'skip_space' : 1,
|
||||
\ 'highlight' : 3,
|
||||
\ 'hi_duration': 200,
|
||||
\ 'command' : [],
|
||||
\ 'linewise' : 1,
|
||||
\ 'autoindent' : -1,
|
||||
\ 'indentkeys' : 0,
|
||||
\ 'indentkeys+': 0,
|
||||
\ 'indentkeys-': 0,
|
||||
\ }
|
||||
let s:default_values.add.block = {
|
||||
\ 'cursor' : 'default',
|
||||
\ 'query_once' : 0,
|
||||
\ 'expr' : 0,
|
||||
\ 'listexpr' : 0,
|
||||
\ 'noremap' : 1,
|
||||
\ 'skip_space' : 1,
|
||||
\ 'highlight' : 3,
|
||||
\ 'hi_duration': 200,
|
||||
\ 'command' : [],
|
||||
\ 'linewise' : 0,
|
||||
\ 'autoindent' : -1,
|
||||
\ 'indentkeys' : 0,
|
||||
\ 'indentkeys+': 0,
|
||||
\ 'indentkeys-': 0,
|
||||
\ }
|
||||
let s:default_values.add.filter = printf('v:key =~# ''\%%(%s\)''', join(keys(s:default_values['add']['char']), '\|'))
|
||||
|
||||
let s:default_values.delete = {}
|
||||
let s:default_values.delete.char = {
|
||||
\ 'cursor' : 'default',
|
||||
\ 'noremap' : 1,
|
||||
\ 'regex' : 0,
|
||||
\ 'skip_space' : 1,
|
||||
\ 'skip_char' : 0,
|
||||
\ 'highlight' : 3,
|
||||
\ 'hi_duration': 200,
|
||||
\ 'command' : [],
|
||||
\ 'linewise' : 0,
|
||||
\ }
|
||||
let s:default_values.delete.line = {
|
||||
\ 'cursor' : 'default',
|
||||
\ 'noremap' : 1,
|
||||
\ 'regex' : 0,
|
||||
\ 'skip_space' : 2,
|
||||
\ 'skip_char' : 0,
|
||||
\ 'highlight' : 3,
|
||||
\ 'hi_duration': 200,
|
||||
\ 'command' : [],
|
||||
\ 'linewise' : 1,
|
||||
\ }
|
||||
let s:default_values.delete.block = {
|
||||
\ 'cursor' : 'default',
|
||||
\ 'noremap' : 1,
|
||||
\ 'regex' : 0,
|
||||
\ 'skip_space' : 1,
|
||||
\ 'skip_char' : 0,
|
||||
\ 'highlight' : 3,
|
||||
\ 'hi_duration': 200,
|
||||
\ 'command' : [],
|
||||
\ 'linewise' : 0,
|
||||
\ }
|
||||
let s:default_values.delete.filter = printf('v:key =~# ''\%%(%s\)''', join(keys(s:default_values['delete']['char']), '\|'))
|
||||
|
||||
let s:default_values.replace = {}
|
||||
let s:default_values.replace.char = {
|
||||
\ 'cursor' : 'default',
|
||||
\ 'query_once' : 0,
|
||||
\ 'regex' : 0,
|
||||
\ 'expr' : 0,
|
||||
\ 'listexpr' : 0,
|
||||
\ 'noremap' : 1,
|
||||
\ 'skip_space' : 1,
|
||||
\ 'skip_char' : 0,
|
||||
\ 'highlight' : 3,
|
||||
\ 'hi_duration': 200,
|
||||
\ 'command' : [],
|
||||
\ 'linewise' : 0,
|
||||
\ 'autoindent' : -1,
|
||||
\ 'indentkeys' : 0,
|
||||
\ 'indentkeys+': 0,
|
||||
\ 'indentkeys-': 0,
|
||||
\ }
|
||||
let s:default_values.replace.line = {
|
||||
\ 'cursor' : 'default',
|
||||
\ 'query_once' : 0,
|
||||
\ 'regex' : 0,
|
||||
\ 'expr' : 0,
|
||||
\ 'listexpr' : 0,
|
||||
\ 'noremap' : 1,
|
||||
\ 'skip_space' : 2,
|
||||
\ 'skip_char' : 0,
|
||||
\ 'highlight' : 3,
|
||||
\ 'hi_duration': 200,
|
||||
\ 'command' : [],
|
||||
\ 'linewise' : 0,
|
||||
\ 'autoindent' : -1,
|
||||
\ 'indentkeys' : 0,
|
||||
\ 'indentkeys+': 0,
|
||||
\ 'indentkeys-': 0,
|
||||
\ }
|
||||
let s:default_values.replace.block = {
|
||||
\ 'cursor' : 'default',
|
||||
\ 'query_once' : 0,
|
||||
\ 'regex' : 0,
|
||||
\ 'expr' : 0,
|
||||
\ 'listexpr' : 0,
|
||||
\ 'noremap' : 1,
|
||||
\ 'skip_space' : 1,
|
||||
\ 'skip_char' : 0,
|
||||
\ 'highlight' : 3,
|
||||
\ 'hi_duration': 200,
|
||||
\ 'command' : [],
|
||||
\ 'linewise' : 0,
|
||||
\ 'autoindent' : -1,
|
||||
\ 'indentkeys' : 0,
|
||||
\ 'indentkeys+': 0,
|
||||
\ 'indentkeys-': 0,
|
||||
\ }
|
||||
let s:default_values.replace.filter = printf('v:key =~# ''\%%(%s\)''', join(keys(s:default_values['replace']['char']), '\|'))
|
||||
|
||||
let s:default_values.textobj = {}
|
||||
let s:default_values.textobj.auto = {
|
||||
\ 'expr' : 0,
|
||||
\ 'listexpr' : 0,
|
||||
\ 'regex' : 0,
|
||||
\ 'skip_regex' : [],
|
||||
\ 'skip_regex_head': [],
|
||||
\ 'skip_regex_tail': [],
|
||||
\ 'quoteescape' : 0,
|
||||
\ 'expand_range' : -1,
|
||||
\ 'nesting' : 0,
|
||||
\ 'synchro' : 1,
|
||||
\ 'noremap' : 1,
|
||||
\ 'syntax' : [],
|
||||
\ 'inner_syntax' : [],
|
||||
\ 'match_syntax' : 0,
|
||||
\ 'skip_break' : 0,
|
||||
\ 'skip_expr' : [],
|
||||
\ }
|
||||
let s:default_values.textobj.query = {
|
||||
\ 'expr' : 0,
|
||||
\ 'listexpr' : 0,
|
||||
\ 'regex' : 0,
|
||||
\ 'skip_regex' : [],
|
||||
\ 'skip_regex_head': [],
|
||||
\ 'skip_regex_tail': [],
|
||||
\ 'quoteescape' : 0,
|
||||
\ 'expand_range' : -1,
|
||||
\ 'nesting' : 0,
|
||||
\ 'synchro' : 1,
|
||||
\ 'noremap' : 1,
|
||||
\ 'syntax' : [],
|
||||
\ 'inner_syntax' : [],
|
||||
\ 'match_syntax' : 0,
|
||||
\ 'skip_break' : 0,
|
||||
\ 'skip_expr' : [],
|
||||
\ }
|
||||
let s:default_values.textobj.filter = printf('v:key =~# ''\%%(%s\)''', join(keys(s:default_values.textobj.auto), '\|'))
|
||||
"}}}
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,41 @@
|
||||
function! sandwich#util#echo(messages) abort "{{{
|
||||
echo ''
|
||||
for [mes, hi_group] in a:messages
|
||||
execute 'echohl ' . hi_group
|
||||
echon mes
|
||||
echohl NONE
|
||||
endfor
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#util#addlocal(recipes) abort "{{{
|
||||
return s:extend_local(a:recipes)
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#util#insertlocal(recipes) abort "{{{
|
||||
return s:extend_local(a:recipes, 0)
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#util#ftrevert(filetype) abort "{{{
|
||||
if exists('b:sandwich_recipes')
|
||||
call filter(b:sandwich_recipes, 'get(v:val, "__filetype__", "") !=# a:filetype')
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
|
||||
function! s:extend_local(recipes, ...) abort "{{{
|
||||
if !exists('b:sandwich_recipes')
|
||||
if exists('g:sandwich#recipes')
|
||||
let b:sandwich_recipes = deepcopy(g:sandwich#recipes)
|
||||
else
|
||||
let b:sandwich_recipes = deepcopy(g:sandwich#default_recipes)
|
||||
endif
|
||||
endif
|
||||
let i = get(a:000, 0, len(b:sandwich_recipes))
|
||||
call extend(b:sandwich_recipes, copy(a:recipes), i)
|
||||
return b:sandwich_recipes
|
||||
endfunction "}}}
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,204 @@
|
||||
" textobj-sandwich: pick up a sandwich!
|
||||
|
||||
function! textobj#sandwich#auto(mode, a_or_i, ...) abort "{{{
|
||||
let kind = 'auto'
|
||||
let defaultopt = s:default_options(kind)
|
||||
let argopt = get(a:000, 0, {})
|
||||
let opt = sandwich#opt#new(kind, defaultopt, argopt)
|
||||
if a:0 >= 2
|
||||
let recipes = textobj#sandwich#recipes#new(kind, a:mode, a:2)
|
||||
else
|
||||
let recipes = textobj#sandwich#recipes#new(kind, a:mode)
|
||||
endif
|
||||
call recipes.uniq(opt)
|
||||
|
||||
if recipes.integrated != []
|
||||
let g:textobj#sandwich#object = textobj#sandwich#textobj#new(
|
||||
\ kind, a:a_or_i, a:mode, v:count1, recipes, opt)
|
||||
let cmd = ":\<C-u>call textobj#sandwich#select()\<CR>"
|
||||
else
|
||||
let cmd = a:mode ==# 'o' ? "\<Esc>" : "\<Ignore>"
|
||||
endif
|
||||
return cmd
|
||||
endfunction
|
||||
"}}}
|
||||
function! textobj#sandwich#query(mode, a_or_i, ...) abort "{{{
|
||||
let kind = 'query'
|
||||
let defaultopt = s:default_options(kind)
|
||||
let argopt = get(a:000, 0, {})
|
||||
let opt = sandwich#opt#new(kind, defaultopt, argopt)
|
||||
if a:0 >= 2
|
||||
let recipes = textobj#sandwich#recipes#new(kind, a:mode, a:2)
|
||||
else
|
||||
let recipes = textobj#sandwich#recipes#new(kind, a:mode)
|
||||
endif
|
||||
let timeout = s:get_sandwich_option('timeout', &timeout)
|
||||
let timeoutlen = max([0, s:get_sandwich_option('timeoutlen', &timeoutlen)])
|
||||
call recipes.query(opt, timeout, timeoutlen)
|
||||
|
||||
if recipes.integrated != []
|
||||
let g:textobj#sandwich#object = textobj#sandwich#textobj#new(
|
||||
\ kind, a:a_or_i, a:mode, v:count1, recipes, opt)
|
||||
let cmd = ":\<C-u>call textobj#sandwich#select()\<CR>"
|
||||
else
|
||||
let cmd = a:mode ==# 'o' ? "\<Esc>" : "\<Ignore>"
|
||||
endif
|
||||
return cmd
|
||||
endfunction
|
||||
"}}}
|
||||
function! textobj#sandwich#select() abort "{{{
|
||||
if !exists('g:textobj#sandwich#object')
|
||||
return
|
||||
endif
|
||||
|
||||
let view = winsaveview()
|
||||
let textobj = g:textobj#sandwich#object
|
||||
call textobj.initialize()
|
||||
let stimeoutlen = max([0, s:get_textobj_option('stimeoutlen', 500)])
|
||||
let [virtualedit, whichwrap] = [&virtualedit, &whichwrap]
|
||||
let [&virtualedit, &whichwrap] = ['onemore', 'h,l']
|
||||
try
|
||||
let candidates = textobj.list(stimeoutlen)
|
||||
let elected = textobj.elect(candidates)
|
||||
call winrestview(view)
|
||||
call textobj.select(elected)
|
||||
finally
|
||||
let mark_latestjump = s:get_textobj_option('latest_jump', 1)
|
||||
call textobj.finalize(mark_latestjump)
|
||||
if !textobj.done
|
||||
call winrestview(view)
|
||||
endif
|
||||
let [&virtualedit, &whichwrap] = [virtualedit, whichwrap]
|
||||
endtry
|
||||
|
||||
let g:textobj#sandwich#object = textobj " This is required in case that textobj-sandwich call textobj-sandwich itself in its recipe.
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
function! s:get_sandwich_option(name, default) abort "{{{
|
||||
if exists('g:textobj#sandwich#' . a:name)
|
||||
return eval('g:textobj#sandwich#' . a:name)
|
||||
endif
|
||||
if exists('g:sandwich#' . a:name)
|
||||
return eval('g:sandwich#' . a:name)
|
||||
endif
|
||||
return a:default
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_textobj_option(name, default) abort "{{{
|
||||
return get(g:, 'textobj#sandwich#' . a:name, a:default)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" recipe "{{{
|
||||
function! textobj#sandwich#get_recipes() abort "{{{
|
||||
if exists('b:textobj_sandwich_recipes')
|
||||
let recipes = b:textobj_sandwich_recipes
|
||||
elseif exists('g:textobj#sandwich#recipes')
|
||||
let recipes = g:textobj#sandwich#recipes
|
||||
else
|
||||
let recipes = g:textobj#sandwich#default_recipes
|
||||
endif
|
||||
return deepcopy(recipes)
|
||||
endfunction
|
||||
"}}}
|
||||
if exists('g:textobj#sandwich#default_recipes')
|
||||
unlockvar! g:textobj#sandwich#default_recipes
|
||||
endif
|
||||
let g:textobj#sandwich#default_recipes = []
|
||||
lockvar! g:textobj#sandwich#default_recipes
|
||||
"}}}
|
||||
|
||||
" option "{{{
|
||||
function! s:default_options(kind) abort "{{{
|
||||
return get(b:, 'textobj_sandwich_options', g:textobj#sandwich#options)[a:kind]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:initialize_options(...) abort "{{{
|
||||
let manner = a:0 ? a:1 : 'keep'
|
||||
let g:textobj#sandwich#options = s:get_textobj_option('options', {})
|
||||
for kind in ['auto', 'query']
|
||||
if !has_key(g:textobj#sandwich#options, kind)
|
||||
let g:textobj#sandwich#options[kind] = {}
|
||||
endif
|
||||
call extend(g:textobj#sandwich#options[kind],
|
||||
\ sandwich#opt#defaults(kind), manner)
|
||||
endfor
|
||||
endfunction
|
||||
call s:initialize_options()
|
||||
"}}}
|
||||
function! textobj#sandwich#set_default() abort "{{{
|
||||
call s:initialize_options('force')
|
||||
endfunction
|
||||
"}}}
|
||||
function! textobj#sandwich#set(kind, option, value) abort "{{{
|
||||
if s:argument_error(a:kind, a:option, a:value)
|
||||
return
|
||||
endif
|
||||
|
||||
if a:kind ==# 'all'
|
||||
let kinds = ['auto', 'query']
|
||||
else
|
||||
let kinds = [a:kind]
|
||||
endif
|
||||
|
||||
for kind in kinds
|
||||
let g:textobj#sandwich#options[kind][a:option] = a:value
|
||||
endfor
|
||||
endfunction
|
||||
"}}}
|
||||
function! textobj#sandwich#setlocal(kind, option, value) abort "{{{
|
||||
if s:argument_error(a:kind, a:option, a:value)
|
||||
return
|
||||
endif
|
||||
|
||||
if !exists('b:textobj_sandwich_options')
|
||||
let b:textobj_sandwich_options = deepcopy(g:textobj#sandwich#options)
|
||||
endif
|
||||
|
||||
if a:kind ==# 'all'
|
||||
let kinds = ['auto', 'query']
|
||||
else
|
||||
let kinds = [a:kind]
|
||||
endif
|
||||
|
||||
for kind in kinds
|
||||
let b:textobj_sandwich_options[kind][a:option] = a:value
|
||||
endfor
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:argument_error(kind, option, value) abort "{{{
|
||||
if !(a:kind ==# 'auto' || a:kind ==# 'query' || a:kind ==# 'all')
|
||||
echohl WarningMsg
|
||||
echomsg 'Invalid kind "' . a:kind . '".'
|
||||
echohl NONE
|
||||
return 1
|
||||
endif
|
||||
|
||||
" NOTE: Regaardless of a:kind, keys(defaults) is fixed since
|
||||
" the two textobjects have same kinds of options.
|
||||
let defaults = sandwich#opt#defaults('auto')
|
||||
if filter(keys(defaults), 'v:val ==# a:option') == []
|
||||
echohl WarningMsg
|
||||
echomsg 'Invalid option name "' . a:kind . '".'
|
||||
echohl NONE
|
||||
return 1
|
||||
endif
|
||||
|
||||
if type(a:value) != type(defaults[a:option])
|
||||
echohl WarningMsg
|
||||
echomsg 'Invalid type of value. ' . string(a:value)
|
||||
echohl NONE
|
||||
return 1
|
||||
endif
|
||||
return 0
|
||||
endfunction
|
||||
"}}}
|
||||
"}}}
|
||||
|
||||
unlet! g:textobj#sandwich#object
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,128 @@
|
||||
" common functions
|
||||
|
||||
" variables "{{{
|
||||
" null valiables
|
||||
let s:null_coord = [0, 0]
|
||||
|
||||
" patchs
|
||||
if v:version > 704 || (v:version == 704 && has('patch237'))
|
||||
let s:has_patch_7_4_358 = has('patch-7.4.358')
|
||||
else
|
||||
let s:has_patch_7_4_358 = v:version == 704 && has('patch358')
|
||||
endif
|
||||
"}}}
|
||||
|
||||
function! s:c2p(coord) abort "{{{
|
||||
return [0] + a:coord + [0]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:escape(string) abort "{{{
|
||||
return escape(a:string, '~"\.^$[]*')
|
||||
endfunction
|
||||
"}}}
|
||||
" function! s:sort(list, func, count) abort "{{{
|
||||
if s:has_patch_7_4_358
|
||||
function! s:sort(list, func, count) abort
|
||||
return sort(a:list, a:func)
|
||||
endfunction
|
||||
else
|
||||
function! s:sort(list, func, count) abort
|
||||
" NOTE: len(a:list) is always larger than count or same.
|
||||
" FIXME: The number of item in a:list would not be large, but if there was
|
||||
" any efficient argorithm, I would rewrite here.
|
||||
let len = len(a:list)
|
||||
for i in range(a:count)
|
||||
if len - 2 >= i
|
||||
let min = len - 1
|
||||
for j in range(len - 2, i, -1)
|
||||
if call(a:func, [a:list[min], a:list[j]]) >= 0
|
||||
let min = j
|
||||
endif
|
||||
endfor
|
||||
|
||||
if min > i
|
||||
call insert(a:list, remove(a:list, min), i)
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
return a:list
|
||||
endfunction
|
||||
endif
|
||||
"}}}
|
||||
function! s:get_displaycoord(coord) abort "{{{
|
||||
let [lnum, col] = a:coord
|
||||
|
||||
if [lnum, col] != s:null_coord
|
||||
let disp_col = col == 1 ? 1 : strdisplaywidth(getline(lnum)[: col - 2]) + 1
|
||||
else
|
||||
let disp_col = 0
|
||||
endif
|
||||
return [lnum, disp_col]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:set_displaycoord(disp_coord) abort "{{{
|
||||
if a:disp_coord != s:null_coord
|
||||
execute 'normal! ' . a:disp_coord[0] . 'G' . a:disp_coord[1] . '|'
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_displaysyntax(coord) abort "{{{
|
||||
return synIDattr(synIDtrans(synID(a:coord[0], a:coord[1], 1)), 'name')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_matched_syntax(coord, syntaxID) abort "{{{
|
||||
if a:coord == s:null_coord
|
||||
return 0
|
||||
elseif a:syntaxID == []
|
||||
return 1
|
||||
else
|
||||
return s:get_displaysyntax(a:coord) ==? a:syntaxID[0]
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_included_syntax(coord, syntaxID) abort "{{{
|
||||
let synstack = map(synstack(a:coord[0], a:coord[1]),
|
||||
\ 'synIDattr(synIDtrans(v:val), "name")')
|
||||
|
||||
if a:syntaxID == []
|
||||
return 1
|
||||
elseif synstack == []
|
||||
if a:syntaxID == ['']
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
endif
|
||||
else
|
||||
return filter(map(copy(a:syntaxID), '''\c'' . v:val'), 'match(synstack, v:val) > -1') != []
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
function! s:export(namelist) abort "{{{
|
||||
let module = {}
|
||||
for name in a:namelist
|
||||
let module[name] = function('s:' . name)
|
||||
endfor
|
||||
return module
|
||||
endfunction
|
||||
"}}}
|
||||
let s:lib = s:export([
|
||||
\ 'c2p',
|
||||
\ 'escape',
|
||||
\ 'sort',
|
||||
\ 'get_displaycoord',
|
||||
\ 'set_displaycoord',
|
||||
\ 'get_displaysyntax',
|
||||
\ 'is_matched_syntax',
|
||||
\ 'is_included_syntax',
|
||||
\ ])
|
||||
lockvar! s:lib
|
||||
|
||||
function! textobj#sandwich#lib#import() abort "{{{
|
||||
return s:lib
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,278 @@
|
||||
" recipes object
|
||||
|
||||
" variables "{{{
|
||||
let s:TRUE = 1
|
||||
let s:FALSE = 0
|
||||
|
||||
" types
|
||||
let s:type_num = type(0)
|
||||
let s:type_str = type('')
|
||||
"}}}
|
||||
|
||||
function! textobj#sandwich#recipes#new(kind, mode, ...) abort "{{{
|
||||
let recipes = deepcopy(s:recipes)
|
||||
if a:0 > 0
|
||||
let recipes.arg = a:1
|
||||
let recipes.arg_given = 1
|
||||
endif
|
||||
call recipes._integrate(a:kind, a:mode)
|
||||
return recipes
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" s:recipes "{{{
|
||||
let s:recipes = {
|
||||
\ 'arg': [],
|
||||
\ 'arg_given': 0,
|
||||
\ 'integrated': [],
|
||||
\ }
|
||||
"}}}
|
||||
function! s:recipes.uniq(opt) dict abort "{{{
|
||||
let opt_regex = a:opt.of('regex')
|
||||
let opt_expr = a:opt.of('expr')
|
||||
let opt_noremap = a:opt.of('noremap')
|
||||
let recipes = copy(self.integrated)
|
||||
call filter(self.integrated, 0)
|
||||
while recipes != []
|
||||
let recipe = remove(recipes, 0)
|
||||
call add(self.integrated, recipe)
|
||||
if has_key(recipe, 'buns')
|
||||
call filter(recipes, '!s:is_duplicated_buns(v:val, recipe, opt_regex, opt_expr)')
|
||||
elseif has_key(recipe, 'external')
|
||||
call filter(recipes, '!s:is_duplicated_external(v:val, recipe, opt_noremap)')
|
||||
endif
|
||||
endwhile
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:recipes.query(opt, timeout, timeoutlen) dict abort "{{{
|
||||
let recipes = deepcopy(self.integrated)
|
||||
let clock = sandwich#clock#new()
|
||||
let input = ''
|
||||
let last_compl_match = ['', []]
|
||||
while 1
|
||||
let c = getchar(0)
|
||||
if empty(c)
|
||||
if clock.started && a:timeout && a:timeoutlen > 0 && clock.elapsed() > a:timeoutlen
|
||||
let [input, recipes] = last_compl_match
|
||||
break
|
||||
else
|
||||
sleep 20m
|
||||
continue
|
||||
endif
|
||||
endif
|
||||
|
||||
let c = type(c) == s:type_num ? nr2char(c) : c
|
||||
|
||||
" exit loop if <Esc> is pressed
|
||||
if c is# "\<Esc>"
|
||||
let input = "\<Esc>"
|
||||
break
|
||||
endif
|
||||
|
||||
let input .= c
|
||||
|
||||
" check forward match
|
||||
let n_fwd = len(filter(recipes, 's:is_input_matched(v:val, input, a:opt, 0)'))
|
||||
|
||||
" check complete match
|
||||
let n_comp = len(filter(copy(recipes), 's:is_input_matched(v:val, input, a:opt, 1)'))
|
||||
if n_comp || strchars(input) == 1
|
||||
if len(recipes) == n_comp
|
||||
break
|
||||
else
|
||||
call clock.stop()
|
||||
call clock.start()
|
||||
let last_compl_match = [input, copy(recipes)]
|
||||
endif
|
||||
else
|
||||
if clock.started && !n_fwd
|
||||
let [input, recipes] = last_compl_match
|
||||
" FIXME: Additional keypress, 'c', is ignored now, but it should be pressed.
|
||||
" The problem is feedkeys() cannot be used for here...
|
||||
break
|
||||
endif
|
||||
endif
|
||||
if recipes == [] | break | endif
|
||||
endwhile
|
||||
call clock.stop()
|
||||
|
||||
if filter(recipes, 's:is_input_matched(v:val, input, a:opt, 1)') != []
|
||||
let recipe = recipes[0]
|
||||
if has_key(recipe, 'buns') || has_key(recipe, 'external')
|
||||
let self.integrated = [recipe]
|
||||
else
|
||||
let self.integrated = []
|
||||
endif
|
||||
else
|
||||
if s:is_input_fallback(input)
|
||||
let c = split(input, '\zs')[0]
|
||||
let recipe = {'buns': [c, c], 'expr': 0, 'regex': 0}
|
||||
let self.integrated = [recipe]
|
||||
else
|
||||
let self.integrated = []
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:recipes._integrate(kind, mode) dict abort "{{{
|
||||
let self.integrated = []
|
||||
if self.arg_given
|
||||
let self.integrated += self.arg
|
||||
else
|
||||
let self.integrated += sandwich#get_recipes()
|
||||
let self.integrated += textobj#sandwich#get_recipes()
|
||||
endif
|
||||
let filter = 's:has_filetype(v:val)
|
||||
\ && s:has_kind(v:val, a:kind)
|
||||
\ && s:has_mode(v:val, a:mode)
|
||||
\ && s:has_action(v:val)
|
||||
\ && s:expr_filter(v:val)'
|
||||
call filter(self.integrated, filter)
|
||||
call reverse(self.integrated)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" filters
|
||||
function! s:has_filetype(candidate) abort "{{{
|
||||
if !has_key(a:candidate, 'filetype')
|
||||
return 1
|
||||
else
|
||||
let filetypes = split(&filetype, '\.')
|
||||
if filetypes == []
|
||||
let filter = 'v:val ==# "all" || v:val ==# ""'
|
||||
else
|
||||
let filter = 'v:val ==# "all" || index(filetypes, v:val) > -1'
|
||||
endif
|
||||
return filter(copy(a:candidate['filetype']), filter) != []
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:has_kind(candidate, kind) abort "{{{
|
||||
if !has_key(a:candidate, 'kind')
|
||||
return 1
|
||||
else
|
||||
let filter = 'v:val ==# a:kind || v:val ==# "textobj" || v:val ==# "all"'
|
||||
return filter(copy(a:candidate['kind']), filter) != []
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:has_mode(candidate, mode) abort "{{{
|
||||
if !has_key(a:candidate, 'mode')
|
||||
return 1
|
||||
else
|
||||
return stridx(join(a:candidate['mode'], ''), a:mode) > -1
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:has_action(candidate) abort "{{{
|
||||
if !has_key(a:candidate, 'action')
|
||||
return 1
|
||||
endif
|
||||
let filter = 'v:val ==# "delete" || v:val ==# "all"'
|
||||
return filter(copy(a:candidate['action']), filter) != []
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:expr_filter(candidate) abort "{{{
|
||||
if !has_key(a:candidate, 'expr_filter')
|
||||
return 1
|
||||
else
|
||||
for filter in a:candidate['expr_filter']
|
||||
if !eval(filter)
|
||||
return 0
|
||||
endif
|
||||
endfor
|
||||
return 1
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
function! s:is_duplicated_buns(item, ref, opt_regex, opt_expr) abort "{{{
|
||||
if has_key(a:item, 'buns')
|
||||
\ && type(a:ref['buns'][0]) == s:type_str
|
||||
\ && type(a:ref['buns'][1]) == s:type_str
|
||||
\ && type(a:item['buns'][0]) == s:type_str
|
||||
\ && type(a:item['buns'][1]) == s:type_str
|
||||
\ && a:ref['buns'][0] ==# a:item['buns'][0]
|
||||
\ && a:ref['buns'][1] ==# a:item['buns'][1]
|
||||
let regex_r = get(a:ref, 'regex', a:opt_regex)
|
||||
let regex_i = get(a:item, 'regex', a:opt_regex)
|
||||
let expr_r = get(a:ref, 'expr', a:opt_expr)
|
||||
let expr_i = get(a:item, 'expr', a:opt_expr)
|
||||
|
||||
let expr_r = expr_r ? 1 : 0
|
||||
let expr_i = expr_i ? 1 : 0
|
||||
|
||||
if regex_r == regex_i && expr_r == expr_i
|
||||
return 1
|
||||
endif
|
||||
endif
|
||||
return 0
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_duplicated_external(item, ref, opt_noremap) abort "{{{
|
||||
if has_key(a:item, 'external')
|
||||
\ && a:ref['external'][0] ==# a:item['external'][0]
|
||||
\ && a:ref['external'][1] ==# a:item['external'][1]
|
||||
let noremap_r = get(a:ref, 'noremap', a:opt_noremap)
|
||||
let noremap_i = get(a:item, 'noremap', a:opt_noremap)
|
||||
|
||||
if noremap_r == noremap_i
|
||||
return 1
|
||||
endif
|
||||
endif
|
||||
|
||||
return 0
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_input_matched(candidate, input, opt, flag) abort "{{{
|
||||
let has_buns = has_key(a:candidate, 'buns')
|
||||
let has_ext = has_key(a:candidate, 'external') && has_key(a:candidate, 'input')
|
||||
if !(has_buns || has_ext)
|
||||
return 0
|
||||
elseif !a:flag && a:input ==# ''
|
||||
return 1
|
||||
endif
|
||||
|
||||
let candidate = deepcopy(a:candidate)
|
||||
|
||||
if has_buns
|
||||
call a:opt.update('recipe', candidate)
|
||||
|
||||
" 'input' is necessary for 'expr' or 'regex' buns
|
||||
if (a:opt.of('expr') || a:opt.of('regex')) && !has_key(candidate, 'input')
|
||||
return 0
|
||||
endif
|
||||
|
||||
let inputs = copy(get(candidate, 'input', candidate['buns']))
|
||||
elseif has_ext
|
||||
" 'input' is necessary for 'external' textobjects assignment
|
||||
if !has_key(candidate, 'input')
|
||||
return 0
|
||||
endif
|
||||
|
||||
let inputs = copy(a:candidate['input'])
|
||||
endif
|
||||
|
||||
" If a:flag == 0, check forward match. Otherwise, check complete match.
|
||||
if a:flag
|
||||
return filter(inputs, 'v:val ==# a:input') != []
|
||||
else
|
||||
let idx = strlen(a:input) - 1
|
||||
return filter(inputs, 'v:val[: idx] ==# a:input') != []
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_input_fallback(input) abort "{{{
|
||||
if a:input ==# "\<Esc>" || a:input ==# '' || a:input =~# '^[\x80]'
|
||||
return s:FALSE
|
||||
endif
|
||||
let input_fallback = get(g:, 'sandwich#input_fallback', s:TRUE)
|
||||
if !input_fallback
|
||||
return s:FALSE
|
||||
endif
|
||||
return s:TRUE
|
||||
endfunction "}}}
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,344 @@
|
||||
" sandwich object - manage recipe and positions of a sandwiched text
|
||||
|
||||
" variables "{{{
|
||||
" null valiables
|
||||
let s:null_coord = [0, 0]
|
||||
let s:null_4coord = {
|
||||
\ 'head': copy(s:null_coord),
|
||||
\ 'tail': copy(s:null_coord),
|
||||
\ 'inner_head': copy(s:null_coord),
|
||||
\ 'inner_tail': copy(s:null_coord),
|
||||
\ }
|
||||
|
||||
" types
|
||||
let s:type_num = type(0)
|
||||
let s:type_str = type('')
|
||||
let s:type_list = type([])
|
||||
let s:type_fref = type(function('tr'))
|
||||
|
||||
" common functions
|
||||
let s:lib = textobj#sandwich#lib#import()
|
||||
"}}}
|
||||
|
||||
function! textobj#sandwich#sandwich#new(recipe, opt) abort "{{{
|
||||
let sandwich = deepcopy(s:sandwich)
|
||||
let sandwich.opt = copy(a:opt)
|
||||
let sandwich.opt.recipe = {}
|
||||
call sandwich.opt.update('recipe', a:recipe)
|
||||
|
||||
if has_key(a:recipe, 'buns')
|
||||
let sandwich.searchby = 'buns'
|
||||
unlet sandwich.dough
|
||||
let sandwich.dough = deepcopy(a:recipe.buns)
|
||||
elseif has_key(a:recipe, 'external')
|
||||
let sandwich.searchby = 'external'
|
||||
let sandwich.external = deepcopy(a:recipe.external)
|
||||
else
|
||||
return {}
|
||||
endif
|
||||
let sandwich.recipe = a:recipe
|
||||
return sandwich
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" coord object"{{{
|
||||
let s:coord = deepcopy(s:null_4coord)
|
||||
function! s:coord.initialize() dict abort "{{{
|
||||
let self.head = deepcopy(s:null_coord)
|
||||
let self.tail = deepcopy(s:null_coord)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:coord.get_inner(buns, skip_break) dict abort "{{{
|
||||
call cursor(self.head)
|
||||
call search(a:buns[0], 'ce', self.tail[0])
|
||||
normal! l
|
||||
if a:skip_break && col('.') == col([line('.'), '$'])
|
||||
let self.inner_head = searchpos('\_S', '', self.tail[0])
|
||||
else
|
||||
let self.inner_head = getpos('.')[1:2]
|
||||
endif
|
||||
|
||||
call cursor(self.tail)
|
||||
call search(a:buns[1], 'bc', self.head[0])
|
||||
if a:skip_break && (col('.') < 2 || getline(line('.'))[: col('.')-2] =~# '^\s*$')
|
||||
let self.inner_tail = searchpos('\_S', 'be', self.head[0])
|
||||
else
|
||||
if getpos('.')[2] == 1
|
||||
normal! hl
|
||||
else
|
||||
normal! h
|
||||
endif
|
||||
let self.inner_tail = getpos('.')[1:2]
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:coord.next() dict abort "{{{
|
||||
call cursor(self.head)
|
||||
normal! h
|
||||
let self.head = getpos('.')[1:2]
|
||||
endfunction
|
||||
"}}}
|
||||
"}}}
|
||||
" range object"{{{
|
||||
let s:range = {
|
||||
\ 'valid' : 0,
|
||||
\ 'top' : 0,
|
||||
\ 'bottom' : 0,
|
||||
\ 'toplim' : 0,
|
||||
\ 'botlim' : 0,
|
||||
\ 'stride' : &lines,
|
||||
\ 'count' : 1,
|
||||
\ }
|
||||
function! s:range.initialize(cursor, expand_range) dict abort "{{{
|
||||
let filehead = 1
|
||||
let fileend = line('$')
|
||||
let self.valid = 1
|
||||
let self.top = a:cursor[0]
|
||||
let self.bottom = a:cursor[0]
|
||||
if a:expand_range >= 0
|
||||
let self.toplim = max([filehead, a:cursor[0] - a:expand_range])
|
||||
let self.botlim = min([a:cursor[0] + a:expand_range, fileend])
|
||||
else
|
||||
let self.toplim = filehead
|
||||
let self.botlim = fileend
|
||||
endif
|
||||
let self.count = 1
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:range.next() dict abort "{{{
|
||||
if (self.top == 1/0 && self.bottom < 1)
|
||||
\ || (self.top <= self.toplim && self.bottom >= self.botlim)
|
||||
let self.top = 1/0
|
||||
let self.bottom = 0
|
||||
let self.valid = 0
|
||||
else
|
||||
if self.top > self.toplim
|
||||
let self.top = self.top - self.stride
|
||||
let self.top = self.top < self.toplim ? self.toplim : self.top
|
||||
endif
|
||||
if self.bottom < self.botlim
|
||||
let self.bottom = self.bottom + self.stride
|
||||
let self.bottom = self.bottom > self.botlim ? self.botlim : self.bottom
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
"}}}
|
||||
|
||||
" s:sandwich "{{{
|
||||
let s:sandwich = {
|
||||
\ 'buns' : [],
|
||||
\ 'dough' : [],
|
||||
\ 'external' : [],
|
||||
\ 'searchby' : '',
|
||||
\ 'recipe' : {},
|
||||
\ 'cursor' : copy(s:null_coord),
|
||||
\ 'coord' : deepcopy(s:coord),
|
||||
\ 'range' : deepcopy(s:range),
|
||||
\ 'visualmode': '',
|
||||
\ 'syntax_on' : 1,
|
||||
\ 'syntax' : [],
|
||||
\ 'opt' : {},
|
||||
\ 'escaped' : 0,
|
||||
\ 'not_escaped': [],
|
||||
\ }
|
||||
"}}}
|
||||
function! s:sandwich.initialize(cursor, is_syntax_on) dict abort "{{{
|
||||
let self.syntax_on = a:is_syntax_on
|
||||
let self.cursor = a:cursor
|
||||
call self.coord.initialize()
|
||||
call self.range.initialize(a:cursor, self.opt.of('expand_range'))
|
||||
return self
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:sandwich.bake_buns(state, clock) dict abort "{{{
|
||||
let opt_listexpr = self.opt.of('listexpr')
|
||||
let opt_expr = self.opt.of('expr')
|
||||
let opt_regex = self.opt.of('regex')
|
||||
|
||||
call a:clock.pause()
|
||||
if a:state
|
||||
if opt_listexpr
|
||||
let self.buns = eval(self.dough)
|
||||
elseif opt_expr
|
||||
let self.buns = s:evalexpr(self.dough)
|
||||
else
|
||||
let self.buns = self.dough
|
||||
endif
|
||||
else
|
||||
if opt_listexpr >= 2
|
||||
let self.buns = eval(self.dough)
|
||||
let self.escaped = 0
|
||||
elseif opt_expr >= 2
|
||||
let self.buns = s:evalexpr(self.dough)
|
||||
let self.escaped = 0
|
||||
endif
|
||||
endif
|
||||
call a:clock.start()
|
||||
|
||||
if !s:valid_buns(self.buns)
|
||||
return ['', '']
|
||||
endif
|
||||
|
||||
if !self.escaped
|
||||
let self.not_escaped = copy(self.buns)
|
||||
if !opt_regex
|
||||
call map(self.buns, 's:lib.escape(v:val)')
|
||||
let self.escaped = 1
|
||||
endif
|
||||
endif
|
||||
return self.buns
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:sandwich.searchpair_head(stimeoutlen) dict abort "{{{
|
||||
let tailpos = searchpos(self.buns[1], 'cn', self.range.bottom, a:stimeoutlen)
|
||||
let flag = tailpos == getpos('.')[1:2] ? 'b' : 'bc'
|
||||
let stopline = self.range.top
|
||||
return searchpairpos(self.buns[0], '', self.buns[1], flag, 'self.skip(1)', stopline, a:stimeoutlen)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:sandwich.searchpair_tail(stimeoutlen) dict abort "{{{
|
||||
let stopline = self.range.bottom
|
||||
return searchpairpos(self.buns[0], '', self.buns[1], '', 'self.skip(0)', stopline, a:stimeoutlen)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:sandwich.search_head(flag, stimeoutlen) dict abort "{{{
|
||||
return self._searchpos(self.buns[0], a:flag, 1, self.range.top, a:stimeoutlen)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:sandwich.search_tail(flag, stimeoutlen) dict abort "{{{
|
||||
return self._searchpos(self.buns[1], a:flag, 0, self.range.bottom, a:stimeoutlen)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:sandwich._searchpos(pattern, flag, is_head, stopline, stimeoutlen) dict abort "{{{
|
||||
let coord = searchpos(a:pattern, a:flag, a:stopline, a:stimeoutlen)
|
||||
let flag = substitute(a:flag, '\m\Cc', '', 'g')
|
||||
while coord != s:null_coord && self.skip(a:is_head, coord)
|
||||
let coord = searchpos(a:pattern, flag, a:stopline, a:stimeoutlen)
|
||||
endwhile
|
||||
return coord
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:sandwich.skip(is_head, ...) dict abort "{{{
|
||||
" NOTE: for sandwich.searchpairpos()/sandwich.searchpos() functions.
|
||||
let opt = self.opt
|
||||
let checkpos = get(a:000, 0, getpos('.')[1:2])
|
||||
|
||||
if checkpos == s:null_coord
|
||||
return 1
|
||||
endif
|
||||
|
||||
" quoteescape option
|
||||
let skip_patterns = []
|
||||
if opt.of('quoteescape') && "eescape !=# ''
|
||||
for c in split("eescape, '\zs')
|
||||
let c = s:lib.escape(c)
|
||||
let pattern = printf('[^%s]%s\%%(%s%s\)*\zs\%%#', c, c, c, c)
|
||||
let skip_patterns += [pattern]
|
||||
endfor
|
||||
endif
|
||||
|
||||
" skip_regex option
|
||||
let skip_patterns += opt.of('skip_regex')
|
||||
let skip_patterns += a:is_head ? opt.of('skip_regex_head') : opt.of('skip_regex_tail')
|
||||
if skip_patterns != []
|
||||
call cursor(checkpos)
|
||||
for pattern in skip_patterns
|
||||
let skip = searchpos(pattern, 'cn', checkpos[0]) == checkpos
|
||||
if skip
|
||||
return 1
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
|
||||
" syntax, match_syntax option
|
||||
if self.syntax_on
|
||||
if a:is_head || !opt.of('match_syntax')
|
||||
let opt_syntax = opt.of('syntax')
|
||||
if opt_syntax != [] && !s:lib.is_included_syntax(checkpos, opt_syntax)
|
||||
return 1
|
||||
endif
|
||||
else
|
||||
if !s:lib.is_matched_syntax(checkpos, self.syntax)
|
||||
return 1
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
" skip_expr option
|
||||
for Expr in opt.of('skip_expr')
|
||||
if s:eval(Expr, a:is_head, s:lib.c2p(checkpos))
|
||||
return 1
|
||||
endif
|
||||
endfor
|
||||
|
||||
return 0
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:sandwich.check_syntax(coord) dict abort "{{{
|
||||
if !self.syntax_on || !self.opt.of('match_syntax')
|
||||
return
|
||||
endif
|
||||
|
||||
let synitem = s:lib.get_displaysyntax(a:coord)
|
||||
if synitem !=# ''
|
||||
let self.syntax = [synitem]
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:sandwich.export_recipe() dict abort "{{{
|
||||
" For the cooperation with operator-sandwich
|
||||
" NOTE: After visual selection by a user-defined textobject, v:operator is set as ':'
|
||||
" NOTE: 'synchro' option is not valid for visual mode, because there is no guarantee that g:operator#sandwich#object exists.
|
||||
if self.opt.of('synchro') && exists('g:operator#sandwich#object')
|
||||
\ && ((self.searchby ==# 'buns' && v:operator ==# 'g@') || (self.searchby ==# 'external' && v:operator =~# '\%(:\|g@\)'))
|
||||
\ && &operatorfunc =~# '^operator#sandwich#\%(delete\|replace\)'
|
||||
let recipe = self.recipe
|
||||
if self.searchby ==# 'buns'
|
||||
call extend(recipe, {'buns': self.not_escaped}, 'force')
|
||||
call extend(recipe, {'expr': 0}, 'force')
|
||||
call extend(recipe, {'listexpr': 0}, 'force')
|
||||
elseif self.searchby ==# 'external'
|
||||
let excursus = {
|
||||
\ 'count' : self.range.count,
|
||||
\ 'cursor': s:lib.c2p(self.cursor),
|
||||
\ 'coord' : self.coord,
|
||||
\ }
|
||||
call extend(recipe, {'excursus': excursus}, 'force')
|
||||
endif
|
||||
call extend(recipe, {'action': ['delete']}, 'force')
|
||||
else
|
||||
let recipe = {}
|
||||
endif
|
||||
return recipe
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
function! s:evalexpr(dough) abort "{{{
|
||||
let buns = []
|
||||
let buns += [eval(a:dough[0])]
|
||||
if buns[0] !=# ''
|
||||
let buns += [eval(a:dough[1])]
|
||||
endif
|
||||
return buns
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:valid_buns(buns) abort "{{{
|
||||
return type(a:buns) == s:type_list && len(a:buns) >= 2 && s:check_a_bun(a:buns[0]) && s:check_a_bun(a:buns[1])
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:check_a_bun(bun) abort "{{{
|
||||
let type_bun = type(a:bun)
|
||||
return (type_bun ==# s:type_str && a:bun !=# '') || type_bun ==# s:type_num
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:eval(expr, ...) abort "{{{
|
||||
return type(a:expr) == s:type_fref ? call(a:expr, a:000) : eval(a:expr)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,592 @@
|
||||
" textobj object - search & select a sandwiched text
|
||||
|
||||
" variables "{{{
|
||||
function! s:SID() abort
|
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$')
|
||||
endfunction
|
||||
let s:SNR = printf("\<SNR>%s_", s:SID())
|
||||
delfunction s:SID
|
||||
|
||||
nnoremap <SID>(v) v
|
||||
nnoremap <SID>(V) V
|
||||
nnoremap <SID>(CTRL-v) <C-v>
|
||||
|
||||
let s:KEY_v = printf('%s(v)', s:SNR)
|
||||
let s:KEY_V = printf('%s(V)', s:SNR)
|
||||
let s:KEY_CTRL_v = printf('%s(CTRL-v)', s:SNR)
|
||||
|
||||
" null valiables
|
||||
let s:null_coord = [0, 0]
|
||||
|
||||
" common functions
|
||||
let s:lib = textobj#sandwich#lib#import()
|
||||
"}}}
|
||||
|
||||
function! textobj#sandwich#textobj#new(kind, a_or_i, mode, count, recipes, opt) abort "{{{
|
||||
let textobj = deepcopy(s:textobj)
|
||||
let textobj.kind = a:kind
|
||||
let textobj.a_or_i = a:a_or_i
|
||||
let textobj.mode = a:mode
|
||||
let textobj.count = a:count
|
||||
let textobj.recipes = a:recipes
|
||||
let textobj.opt = a:opt
|
||||
|
||||
" NOTE: The cursor position should be recorded in textobj#sandwich#auto()/textobj#sandwich#query().
|
||||
" It is impossible to get it in textobj#sandwich#select() if in visual mode.
|
||||
let textobj.cursor = getpos('.')[1:2]
|
||||
return textobj
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" s:textobj "{{{
|
||||
let s:textobj = {
|
||||
\ 'state' : 1,
|
||||
\ 'kind' : '',
|
||||
\ 'a_or_i' : 'i',
|
||||
\ 'mode' : '',
|
||||
\ 'count' : 0,
|
||||
\ 'cursor' : copy(s:null_coord),
|
||||
\ 'view' : {},
|
||||
\ 'recipes': {
|
||||
\ 'arg' : [],
|
||||
\ 'arg_given' : 0,
|
||||
\ 'integrated': [],
|
||||
\ },
|
||||
\ 'visual': {
|
||||
\ 'mode': '',
|
||||
\ 'head': copy(s:null_coord),
|
||||
\ 'tail': copy(s:null_coord),
|
||||
\ },
|
||||
\ 'basket': [],
|
||||
\ 'opt' : {},
|
||||
\ 'done' : 0,
|
||||
\ 'clock' : sandwich#clock#new(),
|
||||
\ }
|
||||
"}}}
|
||||
function! s:textobj.initialize() dict abort "{{{
|
||||
let self.done = 0
|
||||
let self.count = !self.state && self.count != v:count1 ? v:count1 : self.count
|
||||
if self.state
|
||||
let self.basket = map(copy(self.recipes.integrated), 'textobj#sandwich#sandwich#new(v:val, self.opt)')
|
||||
call filter(self.basket, 'v:val != {}')
|
||||
else
|
||||
let self.cursor = getpos('.')[1:2]
|
||||
endif
|
||||
if self.mode ==# 'x'
|
||||
let self.visual.mode = visualmode() ==# "\<C-v>" ? "\<C-v>" : 'v'
|
||||
let self.visual.head = getpos("'<")[1:2]
|
||||
let self.visual.tail = getpos("'>")[1:2]
|
||||
else
|
||||
let self.visual.mode = 'v'
|
||||
endif
|
||||
let is_syntax_on = exists('g:syntax_on') || exists('g:syntax_manual')
|
||||
call map(self.basket, 'v:val.initialize(self.cursor, is_syntax_on)')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:textobj.list(stimeoutlen) dict abort "{{{
|
||||
let clock = self.clock
|
||||
|
||||
" gather candidates
|
||||
let candidates = []
|
||||
call clock.stop()
|
||||
call clock.start()
|
||||
while filter(copy(self.basket), 'v:val.range.valid') != []
|
||||
for sandwich in self.basket
|
||||
let candidates += self.search(sandwich, a:stimeoutlen)
|
||||
endfor
|
||||
call s:uniq_candidates(candidates, self.a_or_i)
|
||||
if len(candidates) >= self.count
|
||||
break
|
||||
endif
|
||||
|
||||
" time out
|
||||
if clock.started && a:stimeoutlen > 0
|
||||
let elapsed = clock.elapsed()
|
||||
if elapsed > a:stimeoutlen
|
||||
echo 'textobj-sandwich: Timed out.'
|
||||
break
|
||||
endif
|
||||
endif
|
||||
endwhile
|
||||
call clock.stop()
|
||||
return candidates
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:textobj.search(sandwich, stimeoutlen) dict abort "{{{
|
||||
if a:sandwich.searchby ==# 'buns'
|
||||
if a:sandwich.opt.of('nesting')
|
||||
let candidates = self._search_with_nest(a:sandwich, a:stimeoutlen)
|
||||
else
|
||||
let candidates = self._search_without_nest(a:sandwich, a:stimeoutlen)
|
||||
endif
|
||||
elseif a:sandwich.searchby ==# 'external'
|
||||
let candidates = self._get_region(a:sandwich, a:stimeoutlen)
|
||||
else
|
||||
let candidates = []
|
||||
endif
|
||||
return candidates
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:textobj._search_with_nest(sandwich, stimeoutlen) dict abort "{{{
|
||||
let buns = a:sandwich.bake_buns(self.state, self.clock)
|
||||
let range = a:sandwich.range
|
||||
let coord = a:sandwich.coord
|
||||
let opt = a:sandwich.opt
|
||||
let candidates = []
|
||||
|
||||
if buns[0] ==# '' || buns[1] ==# ''
|
||||
let range.valid = 0
|
||||
endif
|
||||
if !range.valid | return candidates | endif
|
||||
|
||||
" check whether the cursor is on the buns[1] or not
|
||||
call cursor(self.cursor)
|
||||
let _head = searchpos(buns[1], 'bcW', range.top, a:stimeoutlen)
|
||||
let _tail = searchpos(buns[1], 'cenW', range.bottom, a:stimeoutlen)
|
||||
if _head != s:null_coord && _tail != s:null_coord && s:is_in_between(self.cursor, _head, _tail)
|
||||
call cursor(_head)
|
||||
else
|
||||
call cursor(self.cursor)
|
||||
endif
|
||||
|
||||
while 1
|
||||
" search head
|
||||
let head = a:sandwich.searchpair_head(a:stimeoutlen)
|
||||
if head == s:null_coord | break | endif
|
||||
let coord.head = head
|
||||
call a:sandwich.check_syntax(head)
|
||||
|
||||
" search tail
|
||||
let tail = a:sandwich.searchpair_tail(a:stimeoutlen)
|
||||
if tail == s:null_coord | break | endif
|
||||
let tail = searchpos(buns[1], 'ce', range.bottom, a:stimeoutlen)
|
||||
if tail == s:null_coord | break | endif
|
||||
let coord.tail = tail
|
||||
|
||||
" add to candidates
|
||||
call coord.get_inner(buns, opt.of('skip_break'))
|
||||
if self.is_valid_candidate(a:sandwich)
|
||||
let candidate = deepcopy(a:sandwich)
|
||||
let candidate.visualmode = self.visual.mode
|
||||
let candidates += [candidate]
|
||||
endif
|
||||
|
||||
if coord.head == [1, 1]
|
||||
" finish!
|
||||
let range.valid = 0
|
||||
break
|
||||
else
|
||||
call coord.next()
|
||||
endif
|
||||
endwhile
|
||||
call range.next()
|
||||
return candidates
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:textobj._search_without_nest(sandwich, stimeoutlen) dict abort "{{{
|
||||
let buns = a:sandwich.bake_buns(self.state, self.clock)
|
||||
let range = a:sandwich.range
|
||||
let coord = a:sandwich.coord
|
||||
let opt = a:sandwich.opt
|
||||
let candidates = []
|
||||
|
||||
if buns[0] ==# '' || buns[1] ==# ''
|
||||
let range.valid = 0
|
||||
endif
|
||||
if !range.valid | return candidates | endif
|
||||
|
||||
" search nearest head
|
||||
call cursor(self.cursor)
|
||||
let head = a:sandwich.search_head('bc', a:stimeoutlen)
|
||||
if head == s:null_coord
|
||||
call range.next()
|
||||
return candidates
|
||||
endif
|
||||
call a:sandwich.check_syntax(head)
|
||||
let _tail = searchpos(buns[0], 'ce', range.bottom, a:stimeoutlen)
|
||||
|
||||
" search nearest tail
|
||||
call cursor(self.cursor)
|
||||
let tail = a:sandwich.search_tail('ce', a:stimeoutlen)
|
||||
if tail == s:null_coord
|
||||
call range.next()
|
||||
return candidates
|
||||
endif
|
||||
|
||||
" If the cursor is right on a bun
|
||||
if tail == _tail
|
||||
" check whether it is head or tail
|
||||
let odd = 1
|
||||
call cursor([range.top, 1])
|
||||
let pos = searchpos(buns[0], 'c', range.top, a:stimeoutlen)
|
||||
while pos != head && pos != s:null_coord
|
||||
let odd = !odd
|
||||
let pos = searchpos(buns[0], '', range.top, a:stimeoutlen)
|
||||
endwhile
|
||||
if pos == s:null_coord | return candidates | endif
|
||||
|
||||
if odd
|
||||
" pos is head
|
||||
let head = pos
|
||||
call a:sandwich.check_syntax(head)
|
||||
|
||||
" search tail
|
||||
call search(buns[0], 'ce', range.bottom, a:stimeoutlen)
|
||||
let tail = a:sandwich.search_tail('e', a:stimeoutlen)
|
||||
if tail == s:null_coord
|
||||
call range.next()
|
||||
return candidates
|
||||
endif
|
||||
else
|
||||
" pos is tail
|
||||
call cursor(pos)
|
||||
let tail = a:sandwich.search_tail('ce', a:stimeoutlen)
|
||||
call a:sandwich.check_syntax(tail)
|
||||
|
||||
" search head
|
||||
call search(buns[1], 'bc', range.top, a:stimeoutlen)
|
||||
let head = a:sandwich.search_head('b', a:stimeoutlen)
|
||||
if head == s:null_coord
|
||||
call range.next()
|
||||
return candidates
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
let coord.head = head
|
||||
let coord.tail = tail
|
||||
call coord.get_inner(buns, a:sandwich.opt.of('skip_break'))
|
||||
|
||||
if self.is_valid_candidate(a:sandwich)
|
||||
let candidate = deepcopy(a:sandwich)
|
||||
let candidate.visualmode = self.visual.mode
|
||||
let candidates += [candidate]
|
||||
endif
|
||||
let range.valid = 0
|
||||
return candidates
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:textobj._get_region(sandwich, stimeoutlen) dict abort "{{{
|
||||
" NOTE: Because of the restriction of vim, if a operator to get the assigned
|
||||
" region is employed for 'external' user-defined textobjects, it makes
|
||||
" impossible to repeat by dot command. Thus, 'external' is checked by
|
||||
" using visual selection xmap in any case.
|
||||
let range = a:sandwich.range
|
||||
let coord = a:sandwich.coord
|
||||
let opt = a:sandwich.opt
|
||||
let candidates = []
|
||||
|
||||
if !range.valid | return candidates | endif
|
||||
|
||||
if opt.of('noremap')
|
||||
let cmd = 'normal!'
|
||||
let v = self.visual.mode
|
||||
else
|
||||
let cmd = 'normal'
|
||||
let v = self.visual.mode ==# 'v' ? s:KEY_v :
|
||||
\ self.visual.mode ==# 'V' ? s:KEY_V :
|
||||
\ s:KEY_CTRL_v
|
||||
endif
|
||||
|
||||
if self.mode ==# 'x'
|
||||
let initpos = [self.visual.head, self.visual.tail]
|
||||
else
|
||||
let initpos = [self.cursor, self.cursor]
|
||||
endif
|
||||
|
||||
let selection = &selection
|
||||
set selection=inclusive
|
||||
try
|
||||
while 1
|
||||
let [prev_head, prev_tail] = [coord.head, coord.tail]
|
||||
let [prev_inner_head, prev_inner_tail] = [coord.inner_head, coord.inner_tail]
|
||||
" get outer positions
|
||||
let [head, tail, visualmode_a] = s:get_textobj_region(initpos, cmd, v, range.count, a:sandwich.external[1])
|
||||
|
||||
" get inner positions
|
||||
if head != s:null_coord && tail != s:null_coord
|
||||
let [inner_head, inner_tail, visualmode_i] = s:get_textobj_region(initpos, cmd, v, range.count, a:sandwich.external[0])
|
||||
else
|
||||
let [inner_head, inner_tail, visualmode_i] = [copy(s:null_coord), copy(s:null_coord), 'v']
|
||||
endif
|
||||
|
||||
if (self.a_or_i ==# 'i' && s:is_valid_region(inner_head, inner_tail, prev_inner_head, prev_inner_tail, range.count))
|
||||
\ || (self.a_or_i ==# 'a' && s:is_valid_region(head, tail, prev_head, prev_tail, range.count))
|
||||
if head[0] >= range.top && tail[0] <= range.bottom
|
||||
let coord.head = head
|
||||
let coord.tail = tail
|
||||
let coord.inner_head = inner_head
|
||||
let coord.inner_tail = inner_tail
|
||||
|
||||
if self.is_valid_candidate(a:sandwich)
|
||||
let candidate = deepcopy(a:sandwich)
|
||||
let candidate.visualmode = self.a_or_i ==# 'a' ? visualmode_a : visualmode_i
|
||||
let candidates += [candidate]
|
||||
endif
|
||||
else
|
||||
call range.next()
|
||||
break
|
||||
endif
|
||||
else
|
||||
let range.valid = 0
|
||||
break
|
||||
endif
|
||||
|
||||
let range.count += 1
|
||||
endwhile
|
||||
finally
|
||||
" restore visualmode
|
||||
execute 'normal! ' . self.visual.mode
|
||||
execute "normal! \<Esc>"
|
||||
call cursor(self.cursor)
|
||||
" restore marks
|
||||
call setpos("'<", s:lib.c2p(self.visual.head))
|
||||
call setpos("'>", s:lib.c2p(self.visual.tail))
|
||||
" restore options
|
||||
let &selection = selection
|
||||
endtry
|
||||
return candidates
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:textobj.is_valid_candidate(sandwich) dict abort "{{{
|
||||
let coord = a:sandwich.coord
|
||||
if !s:is_in_between(self.cursor, coord.head, coord.tail)
|
||||
return 0
|
||||
endif
|
||||
|
||||
if self.a_or_i ==# 'i'
|
||||
let [head, tail] = [coord.inner_head, coord.inner_tail]
|
||||
else
|
||||
let [head, tail] = [coord.head, coord.tail]
|
||||
endif
|
||||
if head == s:null_coord || tail == s:null_coord || s:is_ahead(head, tail)
|
||||
return 0
|
||||
endif
|
||||
|
||||
" specific condition in visual mode
|
||||
if self.mode !=# 'x'
|
||||
let visual_mode_affair = 1
|
||||
else
|
||||
let visual_mode_affair = s:visual_mode_affair(
|
||||
\ head, tail, self.a_or_i, self.cursor, self.visual)
|
||||
endif
|
||||
if !visual_mode_affair
|
||||
return 0
|
||||
endif
|
||||
|
||||
" specific condition for the option 'matched_syntax' and 'inner_syntax'
|
||||
let opt_syntax_affair = s:opt_syntax_affair(a:sandwich)
|
||||
if !opt_syntax_affair
|
||||
return 0
|
||||
endif
|
||||
|
||||
return 1
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:textobj.elect(candidates) dict abort "{{{
|
||||
let elected = {}
|
||||
if len(a:candidates) >= self.count
|
||||
" election
|
||||
let cursor = self.cursor
|
||||
let map_rule = 'extend(v:val, {"len": s:representative_length(v:val.coord, cursor)})'
|
||||
call map(a:candidates, map_rule)
|
||||
call s:lib.sort(a:candidates, function('s:compare_buf_length'), self.count)
|
||||
let elected = a:candidates[self.count - 1]
|
||||
endif
|
||||
return elected
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:textobj.select(sandwich) dict abort "{{{
|
||||
if a:sandwich == {}
|
||||
if self.mode ==# 'x'
|
||||
normal! gv
|
||||
endif
|
||||
let self.done = 1
|
||||
return
|
||||
endif
|
||||
|
||||
let head = self.a_or_i ==# 'i' ? a:sandwich.coord.inner_head : a:sandwich.coord.head
|
||||
let tail = self.a_or_i ==# 'i' ? a:sandwich.coord.inner_tail : a:sandwich.coord.tail
|
||||
if self.mode ==# 'x' && self.visual.mode ==# "\<C-v>"
|
||||
" trick for the blockwise visual mode
|
||||
if self.cursor[0] == self.visual.tail[0]
|
||||
let disp_coord = s:lib.get_displaycoord(head)
|
||||
let disp_coord[0] = self.visual.head[0]
|
||||
call s:lib.set_displaycoord(disp_coord)
|
||||
let head = getpos('.')[1:2]
|
||||
elseif self.cursor[0] == self.visual.head[0]
|
||||
let disp_coord = s:lib.get_displaycoord(tail)
|
||||
let disp_coord[0] = self.visual.tail[0]
|
||||
call s:lib.set_displaycoord(disp_coord)
|
||||
let tail = getpos('.')[1:2]
|
||||
endif
|
||||
endif
|
||||
|
||||
execute 'normal! ' . a:sandwich.visualmode
|
||||
call cursor(head)
|
||||
normal! o
|
||||
call cursor(tail)
|
||||
|
||||
" counter measure for the 'selection' option being 'exclusive'
|
||||
if &selection ==# 'exclusive'
|
||||
normal! l
|
||||
endif
|
||||
|
||||
call operator#sandwich#synchronize(self.kind, a:sandwich.export_recipe())
|
||||
let self.done = 1
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:textobj.finalize(mark_latestjump) dict abort "{{{
|
||||
if self.done && a:mark_latestjump
|
||||
call setpos("''", s:lib.c2p(self.cursor))
|
||||
endif
|
||||
|
||||
" flash echoing
|
||||
if !self.state
|
||||
echo ''
|
||||
endif
|
||||
|
||||
let self.state = 0
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
function! s:is_valid_region(head, tail, prev_head, prev_tail, count) abort "{{{
|
||||
return a:head != s:null_coord && a:tail != s:null_coord && (a:count == 1 || s:is_ahead(a:prev_head, a:head) || s:is_ahead(a:tail, a:prev_tail))
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_textobj_region(initpos, cmd, visualmode, count, key_seq) abort "{{{
|
||||
call cursor(a:initpos[0])
|
||||
execute printf('silent! %s %s', a:cmd, a:visualmode)
|
||||
call cursor(a:initpos[1])
|
||||
execute printf('silent! %s %d%s', a:cmd, a:count, a:key_seq)
|
||||
if mode() ==? 'v' || mode() ==# "\<C-v>"
|
||||
execute "normal! \<Esc>"
|
||||
else
|
||||
return [copy(s:null_coord), copy(s:null_coord), a:visualmode]
|
||||
endif
|
||||
let visualmode = visualmode()
|
||||
let [head, tail] = [getpos("'<")[1:2], getpos("'>")[1:2]]
|
||||
if head == a:initpos[0] && tail == a:initpos[1]
|
||||
let [head, tail] = [copy(s:null_coord), copy(s:null_coord)]
|
||||
elseif visualmode ==# 'V'
|
||||
let tail[2] = col([tail[1], '$'])
|
||||
endif
|
||||
return [head, tail, visualmode]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_buf_length(start, end) abort "{{{
|
||||
if a:start[0] == a:end[0]
|
||||
let len = a:end[1] - a:start[1] + 1
|
||||
else
|
||||
let len = (line2byte(a:end[0]) + a:end[1]) - (line2byte(a:start[0]) + a:start[1]) + 1
|
||||
endif
|
||||
return len
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:uniq_candidates(candidates, a_or_i) abort "{{{
|
||||
let i = 0
|
||||
if a:a_or_i ==# 'i'
|
||||
let filter = 'v:val.coord.inner_head != candidate.coord.inner_head || v:val.coord.inner_tail != candidate.coord.inner_tail'
|
||||
else
|
||||
let filter = 'v:val.coord.head == candidate.coord.head || v:val.coord.tail == candidate.coord.tail'
|
||||
endif
|
||||
while i+1 < len(a:candidates)
|
||||
let candidate = a:candidates[i]
|
||||
call filter(a:candidates[i+1 :], filter)
|
||||
let i += 1
|
||||
endwhile
|
||||
return a:candidates
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:visual_mode_affair(head, tail, a_or_i, cursor, visual) abort "{{{
|
||||
" a:visual.mode ==# 'V' never comes.
|
||||
if a:visual.mode ==# 'v'
|
||||
" character-wise
|
||||
if a:a_or_i ==# 'i'
|
||||
let visual_mode_affair = s:is_ahead(a:visual.head, a:head)
|
||||
\ || s:is_ahead(a:tail, a:visual.tail)
|
||||
else
|
||||
let visual_mode_affair = (s:is_ahead(a:visual.head, a:head) && s:is_equal_or_ahead(a:tail, a:visual.tail))
|
||||
\ || (s:is_equal_or_ahead(a:visual.head, a:head) && s:is_ahead(a:tail, a:visual.tail))
|
||||
endif
|
||||
else
|
||||
" block-wise
|
||||
let orig_pos = getpos('.')
|
||||
let visual_head = s:lib.get_displaycoord(a:visual.head)
|
||||
let visual_tail = s:lib.get_displaycoord(a:visual.tail)
|
||||
call s:lib.set_displaycoord([a:cursor[0], visual_head[1]])
|
||||
let thr_head = getpos('.')
|
||||
call s:lib.set_displaycoord([a:cursor[0], visual_tail[1]])
|
||||
let thr_tail = getpos('.')
|
||||
let visual_mode_affair = s:is_ahead(thr_head, a:head)
|
||||
\ || s:is_ahead(a:tail, thr_tail)
|
||||
call setpos('.', orig_pos)
|
||||
endif
|
||||
return visual_mode_affair
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:opt_syntax_affair(sandwich) abort "{{{
|
||||
if !a:sandwich.syntax_on
|
||||
return 1
|
||||
endif
|
||||
|
||||
let coord = a:sandwich.coord
|
||||
let opt = a:sandwich.opt
|
||||
if opt.of('match_syntax') == 2
|
||||
let opt_syntax_affair = s:lib.is_included_syntax(coord.inner_head, a:sandwich.syntax)
|
||||
\ && s:lib.is_included_syntax(coord.inner_tail, a:sandwich.syntax)
|
||||
elseif opt.of('match_syntax') == 3
|
||||
" check inner syntax independently
|
||||
if opt.of('inner_syntax') == []
|
||||
let syntax = [s:lib.get_displaysyntax(coord.inner_head)]
|
||||
let opt_syntax_affair = s:lib.is_included_syntax(coord.inner_tail, syntax)
|
||||
else
|
||||
if s:lib.is_included_syntax(coord.inner_head, opt.of('inner_syntax'))
|
||||
let syntax = [s:lib.get_displaysyntax(coord.inner_head)]
|
||||
let opt_syntax_affair = s:lib.is_included_syntax(coord.inner_tail, syntax)
|
||||
else
|
||||
let opt_syntax_affair = 0
|
||||
endif
|
||||
endif
|
||||
else
|
||||
if opt.of('inner_syntax') == []
|
||||
let opt_syntax_affair = 1
|
||||
else
|
||||
let opt_syntax_affair = s:lib.is_included_syntax(coord.inner_head, opt.of('inner_syntax'))
|
||||
\ && s:lib.is_included_syntax(coord.inner_tail, opt.of('inner_syntax'))
|
||||
endif
|
||||
endif
|
||||
return opt_syntax_affair
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_in_between(coord, head, tail) abort "{{{
|
||||
return (a:coord != s:null_coord) && (a:head != s:null_coord) && (a:tail != s:null_coord)
|
||||
\ && ((a:coord[0] > a:head[0]) || ((a:coord[0] == a:head[0]) && (a:coord[1] >= a:head[1])))
|
||||
\ && ((a:coord[0] < a:tail[0]) || ((a:coord[0] == a:tail[0]) && (a:coord[1] <= a:tail[1])))
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_ahead(coord1, coord2) abort "{{{
|
||||
return a:coord1[0] > a:coord2[0] || (a:coord1[0] == a:coord2[0] && a:coord1[1] > a:coord2[1])
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_equal_or_ahead(coord1, coord2) abort "{{{
|
||||
return a:coord1[0] > a:coord2[0] || (a:coord1[0] == a:coord2[0] && a:coord1[1] >= a:coord2[1])
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:representative_length(coord, cursor) abort "{{{
|
||||
let inner_head = a:coord.inner_head
|
||||
let inner_tail = a:coord.inner_tail
|
||||
if s:is_in_between(a:cursor, inner_head, inner_tail)
|
||||
return s:get_buf_length(inner_head, inner_tail)
|
||||
else
|
||||
return s:get_buf_length(a:coord.head, a:coord.tail)
|
||||
endif
|
||||
endfunction "}}}
|
||||
function! s:compare_buf_length(i1, i2) abort "{{{
|
||||
return a:i1.len - a:i2.len
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,96 @@
|
||||
=============================================================================
|
||||
COMPOUND RECIPES *sandwich-compound-recipes*
|
||||
|
||||
A compound recipe is one that contains multiple simple recipes. A simple
|
||||
recipe could be a pair of parentheses `()` or braces `{}`, while a compound
|
||||
recipe could be made up of all types of brackets `(),[],{}`, or both types of
|
||||
quotes `'',""`.
|
||||
|
||||
sandwich.vim allows for the creation of compound recipes. This tutorial
|
||||
demonstrates the creation of two compound recipes, brackets and quotes.
|
||||
Users can adapt the code provided to customize their own compound recipes.
|
||||
|
||||
|
||||
Compound text-objects~
|
||||
|
||||
By default, sandwich.vim can automatically search for a set of surroundings.
|
||||
This functionality is provided by the `srb` and `sdb` |sandwich-keymappings|.
|
||||
Under the hood, these mappings call |textobj#sandwich#auto()|.
|
||||
|
||||
This function |textobj#sandwich#auto()| also accepts an optional fourth
|
||||
argument. If a list of (simple) recipes is given to the fourth argument, this
|
||||
list is used instead.
|
||||
|
||||
We create a list of recipes for the brackets >
|
||||
let g:sandwich_bracket_recipes = [
|
||||
\ {'buns': ['{', '}'], 'nesting': 1, 'skip_break': 1},
|
||||
\ {'buns': ['[', ']'], 'nesting': 1},
|
||||
\ {'buns': ['(', ')'], 'nesting': 1},
|
||||
\ ]
|
||||
<
|
||||
and another for the quotes >
|
||||
let g:sandwich_quote_recipes = [
|
||||
\ {'buns': ['"', '"'], 'quoteescape': 1, 'expand_range': 0, 'nesting': 0, 'linewise': 0},
|
||||
\ {'buns': ["'", "'"], 'quoteescape': 1, 'expand_range': 0, 'nesting': 0, 'linewise': 0},
|
||||
\ ]
|
||||
<
|
||||
|
||||
Then, we pass these lists of (simple) recipes into |textobj#sandwich#auto()|
|
||||
to create our text-objects. It is also convenient to access these
|
||||
text-objects with key mappings. In this tutorial, we assign the mappings `ij,
|
||||
aj` and `io, ao` to the brackets and quotes text-objects respectively >
|
||||
|
||||
onoremap <silent><expr> ij textobj#sandwich#auto('x', 'i', {}, g:sandwich_bracket_recipes)
|
||||
onoremap <silent><expr> aj textobj#sandwich#auto('x', 'a', {}, g:sandwich_bracket_recipes)
|
||||
xnoremap <silent><expr> ij textobj#sandwich#auto('x', 'i', {}, g:sandwich_bracket_recipes)
|
||||
xnoremap <silent><expr> aj textobj#sandwich#auto('x', 'a', {}, g:sandwich_bracket_recipes)
|
||||
|
||||
onoremap <silent><expr> io textobj#sandwich#auto('x', 'i', {}, g:sandwich_quote_recipes)
|
||||
onoremap <silent><expr> ao textobj#sandwich#auto('x', 'a', {}, g:sandwich_quote_recipes)
|
||||
xnoremap <silent><expr> io textobj#sandwich#auto('x', 'i', {}, g:sandwich_quote_recipes)
|
||||
xnoremap <silent><expr> ao textobj#sandwich#auto('x', 'a', {}, g:sandwich_quote_recipes)
|
||||
<
|
||||
|
||||
With these, one can visually select the nearest pair of brackets with `vaj`.
|
||||
In a similar manner, `dio` deletes the text between the two nearest quotes.
|
||||
|
||||
Compound recipes~
|
||||
|
||||
The next step is to add these text objects as compound recipes, and use them
|
||||
with sandwich operators such as |<Plug>(operator-sandwich-delete)| and
|
||||
|<Plug>(operator-sandwich-replace)| (default: `sd` and `sr`).
|
||||
|
||||
We define these compound recipes using external requisites (see
|
||||
|textobj-sandwich-configuration| or |operator-sandwich-configuration|). The
|
||||
text objects defined above and are passed into the `'external'` item, as seen
|
||||
below >
|
||||
|
||||
let g:sandwich_compound_recipes = [
|
||||
\ {
|
||||
\ 'external': ['ij', 'aj'],
|
||||
\ 'kind' : ['delete', 'replace', 'query'],
|
||||
\ 'noremap' : 0,
|
||||
\ 'input' : ['j'],
|
||||
\ },
|
||||
\ {
|
||||
\ 'external': ['io', 'ao'],
|
||||
\ 'kind' : ['delete', 'replace', 'query'],
|
||||
\ 'noremap' : 0,
|
||||
\ 'input' : ['o'],
|
||||
\ },
|
||||
\ ]
|
||||
<
|
||||
These recipes require an `'input'`, which we specify to be `'j'` and `'o'`
|
||||
for brackets and quotes respectively.
|
||||
|
||||
Finally, we add these compound recipes to |g:sandwich#recipes|,
|
||||
that is, the list of recipes used by sandwich.vim >
|
||||
|
||||
call extend(g:sandwich#recipes, g:sandwich_compound_recipes)
|
||||
<
|
||||
|
||||
With these, one can delete the nearest pair of quotes simply with `sdo`.
|
||||
Similarly, one can replace the nearest pair of brackets with `srj{char}`.
|
||||
|
||||
==============================================================================
|
||||
vim:tw=78:ts=8:noet:ft=help:norl:
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
687
config/neovim/store/lazy-plugins/vim-sandwich/doc/sandwich.jax
Normal file
687
config/neovim/store/lazy-plugins/vim-sandwich/doc/sandwich.jax
Normal file
@ -0,0 +1,687 @@
|
||||
*sandwich.jax* 日本語ヘルプ Last change:14-May-2022.
|
||||
|
||||
“挟まれた”テキストを編集するためのオペレータとテキストオブジェクトの詰め合わせ
|
||||
です。
|
||||
|
||||
書いた人 : machakann <mckn{at}outlook.jp>
|
||||
ライセンス : NYSL license
|
||||
日本語 <http://www.kmonos.net/nysl/>
|
||||
English (Unofficial) <http://www.kmonos.net/nysl/index.en.html>
|
||||
|
||||
必須要件: Vim 7.4 かそれ以降のバージョン
|
||||
|+reltime| 機能 (任意)
|
||||
|+float| 機能 (任意)
|
||||
|
||||
==============================================================================
|
||||
CONTENTS *sandwich-contents*
|
||||
|
||||
QUICK START |sandwich-quick-start|
|
||||
INTRODUCTION |sandwich-introduction|
|
||||
KEYMAPPINGS |sandwich-keymappings|
|
||||
CONFIGURATION |sandwich-configuration|
|
||||
MAGICCHARACTERS |sandwich-magiccharacters|
|
||||
FILETYPE RECIPES |sandwich-filetype-recipes|
|
||||
FUNCTIONS |sandwich-functions|
|
||||
MISCELLANEOUS |sandwich-miscellaneous|
|
||||
vim-surround のキ-マッピングを使う
|
||||
magicchar-f の挙動を変更する
|
||||
|
||||
==============================================================================
|
||||
QUICK START *sandwich-quick-start*
|
||||
|
||||
*sandwich.vim* は文字列を括弧などで囲む/囲みを外す/囲みを置き換えることを目的
|
||||
としたオペレータとテキストオブジェクトのセットです。例えば、(foo) や "bar" の
|
||||
ような文字列が処理の対象になります。
|
||||
|
||||
囲む~
|
||||
sa{motion/textobject}{addition} と、キー入力します。
|
||||
例えば、 foo という単語にカーソルを合わせて saiw( と入力すると (foo) となりま
|
||||
す。
|
||||
|
||||
囲みを外す~
|
||||
sdb あるいは sd{deletion} とキー入力します。
|
||||
例えば、 (foo) というテキストにカーソルを合わせて sdb あるいは sd( と入力する
|
||||
と foo となります。 sdb という入力は自動的に囲まれたテキストを検索します。
|
||||
|
||||
囲みを置き換える~
|
||||
srb{addition} あるいは sr{deletion}{addition} キー入力します。
|
||||
例えば、 (foo) というテキストにカーソルを合わせて srb" あるいは sr(" と入力す
|
||||
ると "foo" となります。
|
||||
|
||||
これだけ読めばこのプラグインを使うのに十分でしょう。もし、さらに興味がわいた方
|
||||
、挙動を細かく変更したい方は続く記述およびオペレータとテキストオブジェクトそれ
|
||||
ぞれのヘルプ |operator-sandwich| 、 |textobj-sandwich| をご覧ください。
|
||||
|
||||
|
||||
|
||||
==============================================================================
|
||||
INTRODUCTION *sandwich-introduction*
|
||||
|
||||
このプラグインは文字列を括弧などで囲む/囲みを外す/囲みを置き換えるための機能を
|
||||
提供します。オペレータ部分 |operator-sandwich| とテキストオブジェクト部分
|
||||
|textobj-sandwich| の二つの部分からなり、これらが共働することにより機能を実現
|
||||
します。また、同時にそれぞれが独立したオペレータ/テキストオブジェクトであるの
|
||||
で、ほかのあらゆるオペレータ/テキストオブジェクトと組み合わせることもできます
|
||||
。これらの機能は純粋にオペレータ及びテキストオブジェクトの枠組みを使って実装さ
|
||||
れているので、いかなるライブラリにも依存することなく |.| コマンドによって繰り
|
||||
返すことができます。
|
||||
|
||||
|sandwich.vim| の提供するキーマッピングおよびマッピング変更のための情報につい
|
||||
ては|sandwich-keymappings|を参照してください。
|
||||
|
||||
|sandwich.vim|を構成する部品としての純粋なオペレータについての情報は
|
||||
|operator-sandwich|のヘルプを参照してください。囲みを編集する機能をカスタマイ
|
||||
ズするための情報もこちらのヘルプに詳細があります。
|
||||
|
||||
|sandwich.vim|を構成する部品としての純粋なテキストオブジェクトについての情報は
|
||||
|textobj-sandwich| のヘルプを参照してください。テキストオブジェクトの挙動をカ
|
||||
スタマイズするための情報もこちらのヘルプに詳細があります。
|
||||
|
||||
|
||||
|
||||
==============================================================================
|
||||
KEYMAPPINGS *sandwich-keymappings*
|
||||
|
||||
このプラグインは以下のキーマッピングを提供します。
|
||||
|
||||
機能 デフォルトキーマッピング
|
||||
--------------------------------------------------------------------------
|
||||
囲む sa{motion/textobject}{addition} (ノーマル、ビジュアルモード)
|
||||
-> |<Plug>(sandwich-add)|
|
||||
|
||||
囲みを外す
|
||||
sd{deletion} (ノーマルモード)
|
||||
sd (ビジュアルモード)
|
||||
-> |<Plug>(sandwich-delete)|
|
||||
|
||||
sdb (ノーマルモード)
|
||||
-> |<Plug>(sandwich-delete-auto)|
|
||||
|
||||
囲みを置き換える
|
||||
sr{deletion}{addition} (ノーマルモード)
|
||||
sr{addition} (ビジュアルモード)
|
||||
-> |<Plug>(sandwich-replace)|
|
||||
|
||||
srb{addition} (ノーマルモード)
|
||||
-> |<Plug>(sandwich-replace-auto)|
|
||||
|
||||
テキストオブジェクト
|
||||
ib (オペレータ待機、ビジュアルモード)
|
||||
-> |<Plug>(textobj-sandwich-auto-i)|
|
||||
ab (オペレータ待機、ビジュアルモード)
|
||||
-> |<Plug>(textobj-sandwich-auto-a)|
|
||||
|
||||
is (オペレータ待機、ビジュアルモード)
|
||||
-> |<Plug>(textobj-sandwich-query-i)|
|
||||
as (オペレータ待機、ビジュアルモード)
|
||||
-> |<Plug>(textobj-sandwich-query-a)|
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
NOTE: 誤操作を防ぐため以下の設定を vimrc に追加することを強く推奨します。
|
||||
>
|
||||
nmap s <Nop>
|
||||
xmap s <Nop>
|
||||
<
|
||||
|s| コマンドは |c|l| コマンドによって代替できます。
|
||||
|
||||
デフォルトのマッピングがお気に召さなければ*g:sandwich_no_default_key_mappings*
|
||||
をあなたの vimrc で定義しておいてください。
|
||||
>
|
||||
let g:sandwich_no_default_key_mappings = 1
|
||||
<
|
||||
以下のコードはキーマッピングの開始キーを s から z に変える例です。
|
||||
>
|
||||
let g:sandwich_no_default_key_mappings = 1
|
||||
|
||||
" add
|
||||
nmap za <Plug>(sandwich-add)
|
||||
xmap za <Plug>(sandwich-add)
|
||||
omap za <Plug>(sandwich-add)
|
||||
|
||||
" delete
|
||||
nmap zd <Plug>(sandwich-delete)
|
||||
xmap zd <Plug>(sandwich-delete)
|
||||
nmap zdb <Plug>(sandwich-delete-auto)
|
||||
|
||||
" replace
|
||||
nmap zr <Plug>(sandwich-replace)
|
||||
xmap zr <Plug>(sandwich-replace)
|
||||
nmap zrb <Plug>(sandwich-replace-auto)
|
||||
<
|
||||
必要ならテキストオブジェクトもマッピングしましょう。
|
||||
>
|
||||
" text-objects (if you need)
|
||||
omap ib <Plug>(textobj-sandwich-auto-i)
|
||||
xmap ib <Plug>(textobj-sandwich-auto-i)
|
||||
omap ab <Plug>(textobj-sandwich-auto-a)
|
||||
xmap ab <Plug>(textobj-sandwich-auto-a)
|
||||
|
||||
omap is <Plug>(textobj-sandwich-query-i)
|
||||
xmap is <Plug>(textobj-sandwich-query-i)
|
||||
omap as <Plug>(textobj-sandwich-query-a)
|
||||
xmap as <Plug>(textobj-sandwich-query-a)
|
||||
<
|
||||
|
||||
|
||||
*<Plug>(sandwich-add)*
|
||||
[count1] <Plug>(sandwich-add) [count2] {motion} {addition}
|
||||
指定されたテキストを囲みます。
|
||||
このキーマッピングは[count]を独特なルールで扱います。
|
||||
[count1] は|<Plug>(sandwich-add)|に渡され [count1] 回囲みます。他方で
|
||||
[count2] は通常通り {motion} に渡されます。どちらの[count]も省略可能で
|
||||
す。{addition}は囲みを指定するキーで、例えば saiw( と入力すると単語を
|
||||
丸括弧()で囲みます。
|
||||
|
||||
{Visual} [count] <Plug>(sandwich-add) {addition}
|
||||
[count]回ヴィジュアル選択した範囲を囲みます。
|
||||
|
||||
<Plug>(sandwich-add)はノーマルモード、ヴィジュアルモード、オペレータ待
|
||||
機モードにマップ可能です。デフォルトでは sa にマップされています。
|
||||
>
|
||||
nmap sa <Plug>(sandwich-add)
|
||||
xmap sa <Plug>(sandwich-add)
|
||||
omap sa <Plug>(sandwich-add)
|
||||
<
|
||||
|
||||
|
||||
*<Plug>(sandwich-delete)*
|
||||
[count] <Plug>(sandwich-delete) {deletion}
|
||||
カーソルから最も近い{deletion}に指定される囲みを外します。例えば、
|
||||
sd( と入力するとカーソルから最も近い対応する丸括弧()を削除します。
|
||||
>
|
||||
(foo) -> foo
|
||||
<
|
||||
[count]を指定するとカーソルから[count]番目に近い囲みを削除します。
|
||||
>
|
||||
(foo(bar)baz) cursor is on "bar"
|
||||
|
||||
-- sd( --> (foobarbaz)
|
||||
-- 2sd( --> foo(bar)baz
|
||||
<
|
||||
|
||||
{Visual} [count] <Plug>(sandwich-delete)
|
||||
ヴィジュアル選択範囲の両端にある連続した囲みを[count]回外します。
|
||||
|
||||
<Plug>(sandwich-delete)はノーマルモード、ヴィジュアルモードにマップ可
|
||||
能です。デフォルトでは sd にマップされています。
|
||||
>
|
||||
nmap sd <Plug>(sandwich-delete)
|
||||
xmap sd <Plug>(sandwich-delete)
|
||||
<
|
||||
|
||||
|
||||
*<Plug>(sandwich-delete-auto)*
|
||||
[count] <Plug>(sandwich-delete-auto)
|
||||
カーソルから最も近い囲みを自動的に検索して外します。
|
||||
[count]を指定するとカーソルから[count]番目に近い囲みを削除します。
|
||||
>
|
||||
[foo(bar)baz] cursor is on "bar"
|
||||
|
||||
-- sdb --> [foobarbaz]
|
||||
-- 2sdb --> foo(bar)baz
|
||||
<
|
||||
<Plug>(sandwich-delete-auto)はノーマルモードにマップ可能です。
|
||||
デフォルトでは sdb にマップされています。
|
||||
>
|
||||
nmap sdb <Plug>(sandwich-delete-auto)
|
||||
<
|
||||
|
||||
|
||||
*<Plug>(sandwich-replace)*
|
||||
[count] <Plug>(sandwich-replace) {deletion} {addition}
|
||||
カーソルから最も近い{deletion}に指定される囲みを{addition}に指定される
|
||||
囲みに置換します。例えば、sr([ と入力するとカーソルから最も近い対応す
|
||||
る丸括弧()を角括弧[]に置換します。
|
||||
>
|
||||
(foo) -> [foo]
|
||||
<
|
||||
[count]を指定するとカーソルから[count]番目に近い囲みを置換します。
|
||||
>
|
||||
(foo(bar)baz) cursor is on "bar"
|
||||
|
||||
-- sr([ --> (foo[bar]baz)
|
||||
-- 2sr([ --> [foo(bar)baz]
|
||||
<
|
||||
|
||||
{Visual} [count] <Plug>(sandwich-replace) {addition}
|
||||
ヴィジュアル選択範囲の両端にある連続した囲みを[count]回置換します。
|
||||
|
||||
<Plug>(sandwich-replace)はノーマルモード、ヴィジュアルモードにマップ可
|
||||
能です。デフォルトでは sr にマップされています。
|
||||
>
|
||||
nmap sr <Plug>(sandwich-replace)
|
||||
xmap sr <Plug>(sandwich-replace)
|
||||
<
|
||||
|
||||
|
||||
*<Plug>(sandwich-replace-auto)*
|
||||
[count] <Plug>(sandwich-replace-auto) {addition}
|
||||
カーソルから最も近い囲みを自動的に検索して置換します。
|
||||
[count]を指定するとカーソルから[count]番目に近い囲みを削除します。
|
||||
>
|
||||
[foo(bar)baz] cursor is on "bar"
|
||||
|
||||
-- srb{ --> [foo{bar}baz]
|
||||
-- 2srb{ --> {foo(bar)baz}
|
||||
<
|
||||
<Plug>(sandwich-replace-auto)はノーマルモードにマップ可能です。
|
||||
デフォルトでは srb にマップされています。
|
||||
>
|
||||
nmap srb <Plug>(sandwich-replace-auto)
|
||||
<
|
||||
|
||||
|
||||
==============================================================================
|
||||
CONFIGURATION *sandwich-configuration*
|
||||
|
||||
括弧などのセットとその性質に依るオプションをまとめた情報をレシピ "recipe" と呼
|
||||
びます。一つ一つのレシピは |Dictionary| で、これらを集めた |list| がオペレータ
|
||||
やテキストオブジェクトの動作を決めます。 |g:sandwich#default_recipes| はその一
|
||||
つで、 |operator-sandwich| と |textobj-sandwich| の両方から参照されます。多く
|
||||
の場合、この情報は共有したほうが便利であるためです。 |g:sandwich#recipes| がユ
|
||||
ーザーによって定義された場合こちらがかわりに参照されます。デフォルト設定の
|
||||
|g:sandwich#default_recipes| は |:echo| コマンドによって確認できます。
|
||||
>
|
||||
:echo g:sandwich#default_recipes
|
||||
<
|
||||
|
||||
上記に加え、 |g:operator#sandwich#recipes| と |g:textobj#sandwich#recipes| も
|
||||
レシピを持つことができます。これらは |operator-sandwich| と |textobj-sandwich|
|
||||
のそれぞれからしか参照されません。固有のレシピをおきたい場合に使いましょう。
|
||||
|
||||
レシピの細かい仕様については、オペレータ及びテキストオブジェクトのヘルプ、
|
||||
|operator-sandwich-configuration| 及び |textobj-sandwich-configuration| をご覧
|
||||
ください。
|
||||
|
||||
|
||||
|
||||
g:sandwich#recipes *g:sandwich#recipes*
|
||||
|operator-sandwich| と |textobj-sandwich| の両方から参照されるレシピの
|
||||
リストです。もし存在しなければ |g:sandwich#default_recipes| がかわりに
|
||||
つかわれます。
|
||||
*b:sandwich_recipes*
|
||||
|b:sandwich_recipes| が存在する場合は、 |g:sandwich#recipes| のかわり
|
||||
にそちらが使われます。これはバッファについてローカルな値なので、ファイ
|
||||
ルタイプ固有な設定が増えすぎた時に使うと便利かもしれません。
|
||||
|
||||
|
||||
|
||||
g:sandwich#default_recipes *g:sandwich#default_recipes*
|
||||
デフォルトで用意されたレシピのリストです。 |g:sandwich#recipes| が存在
|
||||
すれば、そちらがかわりにつかわれます。
|
||||
|
||||
この変数は変更を禁止されていますが、 |g:sandwich#recipes| を宣言する際
|
||||
に必要ならコピーすることができます。
|
||||
>
|
||||
:let g:sandwich#recipes = deepcopy(g:sandwich#default_recipes)
|
||||
<
|
||||
|
||||
デフォルトレシピについて~
|
||||
`(`, `)`
|
||||
`[`, `]`
|
||||
`{`, `}`
|
||||
`<`, `>`
|
||||
開き/閉じ括弧は同様に機能します。例えば、 `saiw(` と `saiw)` は同様の
|
||||
結果を与えます。
|
||||
>
|
||||
foo -> (foo)
|
||||
<
|
||||
`sd(` と `sd)` はこの反対のことをします。
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
`'`
|
||||
`"`
|
||||
クオーテーションで文字列を囲みます。
|
||||
>
|
||||
foo -> 'foo'
|
||||
<
|
||||
クオーテーションによる囲みを消す場合 `quoteescape` オプションが考慮さ
|
||||
れます。また、両端が同じ行の中にある必要があります。
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
`<Space>`
|
||||
スペース囲みを消す場合、連続するスペースを一度に消します。
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
`t`, `T`
|
||||
`f`, `F`
|
||||
`i`, `I`
|
||||
|sandwich-magiccharacters| をご覧ください。
|
||||
|
||||
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
Global options~
|
||||
g:sandwich#timeout *g:sandwich#timeout*
|
||||
この変数に偽値が設定されている場合は、オペレータやテキストオブジェクト
|
||||
は一つのレシピを指定する完全な入力がなされるまで待ちます。例えば、下記
|
||||
のレシピを用意します。
|
||||
>
|
||||
let g:sandwich#recipes = [
|
||||
\ {'buns': ['for {', '}'], 'nesting': 1, 'input': ['bf']}
|
||||
\ {'buns': ['if {', '}'], 'nesting': 1, 'input': ['bi']}
|
||||
\ {'buns': ['else {', '}'], 'nesting': 1, 'input': ['be']}
|
||||
\ ]
|
||||
<
|
||||
このオプションが真の場合は `saiwb` とタイプして少し待つと、オペレータ
|
||||
はタイムアウトのため単語を `b` で囲みます。しかし、偽の場合は一つのレ
|
||||
シピを指定する入力が完成するまで待ちます。この変数はオペレータとテキス
|
||||
トオブジェクトの両方に効果を及ぼします。|g:operator#sandwich#timeout|
|
||||
や |g:textobj#sandwich#timeout| が存在する場合はそれらが優先的に使われ
|
||||
ます。この変数が定義されていなければ 'timeout' オプションが代わりに参
|
||||
照されます。
|
||||
関連:|g:sandwich#timeoutlen|
|
||||
|
||||
|
||||
|
||||
g:sandwich#timeoutlen *g:sandwich#timeoutlen*
|
||||
入力に前方一致で重複するレシピが存在する場合に次のキーシーケンスを待つ
|
||||
時間をミリ秒単位で指定します。
|
||||
>
|
||||
let g:sandwich#recipes = [
|
||||
\ {'buns': ['(', ')']}
|
||||
\ {'buns': ['((', '))']}
|
||||
\ ]
|
||||
<
|
||||
saiw( とキー入力するとオペレータは次に ( が入力されるかこの時間だけ待
|
||||
ちます。この間にもう一度 ( を押下すると '((' と '))' のレシピが使われ
|
||||
るでしょう。キーの押下なしでこの時間が過ぎると '(' と ')' のレシピが使
|
||||
われるでしょう。この変数はオペレータとテキストオブジェクトの両方に効果
|
||||
を及ぼします。|g:operator#sandwich#timeoutlen| や
|
||||
|g:textobj#sandwich#timeout| が存在する場合はそれらが優先的に使われま
|
||||
す。この変数が定義されていなければ 'timeoutlen' が代わりに参照されま
|
||||
す。
|
||||
|
||||
タイムアウト機能(|g:operator#sandwich#timeout|,
|
||||
|g:textobj#sandwich#timeout|, |g:sandwich#timeout|, 'timeout')がオフの
|
||||
場合はこのオプションは無視されます。
|
||||
|
||||
|
||||
|
||||
g:sandwich#input_fallback *g:sandwich#input_fallback*
|
||||
このオプションは囲みを追加・削除・置換するためのユーザーの入力に合致す
|
||||
るレシピが存在しない場合の挙動を制御します。このオプションが真のとき、
|
||||
ユーザーの入力に合致するレシピがなければ、入力された文字自体を追加・削
|
||||
除・置換の対象とします。例えば、a という入力に合致するレシピが存在しな
|
||||
くても saiwa と入力すると単語を a で囲みます。
|
||||
>
|
||||
foo -> afooa
|
||||
<
|
||||
この動作が必要ない場合はこのオプションに偽値を設定します。
|
||||
>
|
||||
let g:sandwich#input_fallback = 0
|
||||
<
|
||||
|
||||
|
||||
==============================================================================
|
||||
MAGICCHARACTERS *sandwich-magiccharacters*
|
||||
|
||||
sandwich.vim は {addition}/{deletion} を決めるためにユーザーに入力を促します。
|
||||
これは例えば `(` が `()` のペアを、 `"` が `""` のペアを指しますが、より特殊な
|
||||
用途のための入力がいくつか存在します。
|
||||
|
||||
f~
|
||||
F~
|
||||
関数で囲みます。例えば `saiwf` と入力すると `f` キーの入力の後に関数名
|
||||
の入力を求められます。関数名を入力し <CR> キーを押すと、その関数名付き
|
||||
の丸括弧でテキストオブジェクトを囲みます。関数名の入力中、 <Tab> キー
|
||||
で現在のバッファから関数名を補完できます。
|
||||
>
|
||||
arg -- saiwffunc<CR> --> func(arg)
|
||||
<
|
||||
逆に `sdf` は関数囲みを削除します。
|
||||
>
|
||||
func(arg) -- sdf --> arg
|
||||
<
|
||||
関数がネストしている場合、 `sdf` はカーソル下の関数囲みを削除し、
|
||||
`sdF` は外側の関数囲みを削除します。
|
||||
>
|
||||
cursor is on 'func2':
|
||||
func1(func2(arg)) -- sdf --> func1(arg)
|
||||
-- sdF --> func2(arg)
|
||||
<
|
||||
|
||||
i~
|
||||
I~
|
||||
その時だけの (`I`nstant) 囲みを定義します。 `saiwi` はユーザーに前方囲
|
||||
みと後方囲みの入力を求めます。例えば、 `saiwifoo<CR>bar<CR>` という
|
||||
入力はカーソル下の単語を `foo` と `bar` で囲みます。入力中は <Tab>
|
||||
キーにより現在のバッファから簡単な単語補完を使えます。反対に `sdi` は
|
||||
任意の入力された囲みを削除します。つまり、 `sdifoo<CR>bar<CR>` は
|
||||
`foowordbar` という文字列を `word` にします。これは |.| コマンドによっ
|
||||
て繰り返すことができるので、編集対象の文字列が多く存在する場合に便利か
|
||||
もしれません。
|
||||
`sa{textobj}I` や `sdI` は最後の入力を再利用します。
|
||||
|
||||
|
||||
t~
|
||||
T~
|
||||
`t` および `T` は HTML 様のタグの編集を支援します。 `saiwt` はユーザー
|
||||
にタグの要素名の入力を促し、そのタグで囲みます。 `saiwT` も同様に機能
|
||||
します。
|
||||
>
|
||||
word -- saiwtp<CR> --> <p>word</p>
|
||||
<
|
||||
`sdt` は直近のタグ囲みを削除します。 `sdT` も同様に機能します。
|
||||
>
|
||||
<p>word</p> -- sdt --> word
|
||||
<
|
||||
`t` と `T` は囲みを置換するときに別のふるまいをします。
|
||||
`srtt` はタグの要素名のみを置き換え、属性等は変更しません。
|
||||
`srTT` はタグ全体を置き換えます。
|
||||
|
||||
|
||||
|
||||
==============================================================================
|
||||
FILETYPE RECIPES *sandwich-filetype-recipes*
|
||||
|
||||
Sandwich.vim にはファイルタイプに固有のレシピもあります。これらは 'filetype'
|
||||
オプションが特定の値のときのみ読み込まれ、有効になります。またこれらはユーザー
|
||||
設定を上書きしないので、必要なものだけ残して使うとよいでしょう。設定の本体は
|
||||
ftplugin/{filetype}/sandwich.vim に記述されています。
|
||||
|
||||
もし、それらのファイルを読み込んでほしくない場合は
|
||||
`g:sandwich_no_{filetype}_ftplugin` に真値を設定しておいてください。例えば、
|
||||
tex 固有のレシピが必要ない場合、次の一行を vimrc に加えます。
|
||||
>
|
||||
let g:sandwich_no_tex_ftplugin = 1
|
||||
<
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
plaintex~
|
||||
tex~
|
||||
>
|
||||
囲み 入力
|
||||
`{text}' u'
|
||||
“{text}” u"
|
||||
„{text}“ U"
|
||||
ug
|
||||
u,
|
||||
«{text}» u<
|
||||
uf
|
||||
`{text}' l'
|
||||
l`
|
||||
``{text}'' l"
|
||||
"`{text}"' L"
|
||||
,,{text}`` l,
|
||||
<<{text}>> l<
|
||||
\{{text}\} \{
|
||||
\[{text}\] \[
|
||||
|
||||
\left({text}\right) m(
|
||||
\left[{text}\right] m[
|
||||
\left|{text}\right| m|
|
||||
\left\{{text}\right\} m{
|
||||
\left\langle {text}\right\rangle m<
|
||||
< 'm' 始まりの入力で挿入される囲みはすべて `ma` という入力で削除すること
|
||||
ができます。例えば `sdma` のように使います。
|
||||
>
|
||||
\big({text}\big) M(
|
||||
\big[{text}\big] M[
|
||||
\big|{text}\big| M|
|
||||
\big\{{text}\big\} M{
|
||||
\big\langle {text}\big\rangle M<
|
||||
|
||||
\begingroup{text}\endgroup gr
|
||||
\gr
|
||||
\toprule{text}\bottomrule tr
|
||||
br
|
||||
\tr
|
||||
\br
|
||||
|
||||
\{input}{{text}} c
|
||||
< このレシピはユーザーに入力を求め、入力されたテキストで {input} を置き
|
||||
換えます。
|
||||
>
|
||||
\begin{{input}}{text}\end{{input}} e
|
||||
< このレシピはユーザーに入力を求め、入力されたテキストで {input} を置き
|
||||
換えます。<Tab> キーにより {input} の入力を補完することができます。
|
||||
この時、補完候補は `g:sandwich#filetype#tex#environments`
|
||||
(あるいは存在すれば `b:sandwich#filetype#tex#environments`) から読み込
|
||||
まれます。
|
||||
|
||||
|
||||
==============================================================================
|
||||
FUNCTIONS *sandwich-functions*
|
||||
|
||||
sandwich#util#addlocal({recipes}) *sandwich#util#addlocal()*
|
||||
レシピのリスト {recipes} をそのバッファにのみ有効な設定として追加しま
|
||||
す。この際、既存のグローバルな設定はそのまま受け継がれます。この関数は
|
||||
グローバルな設定をきれいに保ったままファイルタイプに特有な設定を追加す
|
||||
る、などの用途に使えます。{recipes} はレシピのリスト |List| である点は
|
||||
注意してください。
|
||||
>
|
||||
autocmd FileType python call sandwich#util#addlocal([
|
||||
\ {'buns': ['"""', '"""'], 'nesting': 0, 'input': ['3"']},
|
||||
\ ])
|
||||
<
|
||||
|
||||
==============================================================================
|
||||
MISCELLANEOUS *sandwich-miscellaneous*
|
||||
|
||||
vim-surround のキーマッピングを使う~
|
||||
vim-surround (vim script #1697) と同じキーマッピングが使いたければ次の
|
||||
行を vimrc に追加してください。
|
||||
>
|
||||
runtime macros/sandwich/keymap/surround.vim
|
||||
<
|
||||
NOTE: surround.vim とは違い、 `(` と `)` の入力は同じように機能しま
|
||||
す。もし、オリジナルの surround.vim のように `(` の入力により、
|
||||
空白を含んだ括弧で囲みたい場合は次のように書きます。
|
||||
>
|
||||
runtime macros/sandwich/keymap/surround.vim
|
||||
let g:sandwich#recipes += [
|
||||
\ {'buns': ['{ ', ' }'], 'nesting': 1, 'match_syntax': 1,
|
||||
\ 'kind': ['add', 'replace'], 'action': ['add'], 'input': ['{']},
|
||||
\
|
||||
\ {'buns': ['[ ', ' ]'], 'nesting': 1, 'match_syntax': 1,
|
||||
\ 'kind': ['add', 'replace'], 'action': ['add'], 'input': ['[']},
|
||||
\
|
||||
\ {'buns': ['( ', ' )'], 'nesting': 1, 'match_syntax': 1,
|
||||
\ 'kind': ['add', 'replace'], 'action': ['add'], 'input': ['(']},
|
||||
\
|
||||
\ {'buns': ['{\s*', '\s*}'], 'nesting': 1, 'regex': 1,
|
||||
\ 'match_syntax': 1, 'kind': ['delete', 'replace', 'textobj'],
|
||||
\ 'action': ['delete'], 'input': ['{']},
|
||||
\
|
||||
\ {'buns': ['\[\s*', '\s*\]'], 'nesting': 1, 'regex': 1,
|
||||
\ 'match_syntax': 1, 'kind': ['delete', 'replace', 'textobj'],
|
||||
\ 'action': ['delete'], 'input': ['[']},
|
||||
\
|
||||
\ {'buns': ['(\s*', '\s*)'], 'nesting': 1, 'regex': 1,
|
||||
\ 'match_syntax': 1, 'kind': ['delete', 'replace', 'textobj'],
|
||||
\ 'action': ['delete'], 'input': ['(']},
|
||||
\ ]
|
||||
<
|
||||
|
||||
|
||||
ノーマルモードにおける `ys`, `yss`, `yS`, `ds`, `cs` 及び、ビジュアル
|
||||
モードにおける `S` が使用できます。また、 vim-surround にはありません
|
||||
が `dss` と `css` も使用できます。これらは `ds` と `cs` に似ていますが
|
||||
削除/置換対象となる括弧やクオーテーションを自動的に選択します。
|
||||
|
||||
さらに vim-sandwich は便利なテキストオブジェクトが独立して使用できま
|
||||
す。ぜひ試してみてください。
|
||||
|
||||
* ユーザーの入力に従い、括弧や同じ文字に囲まれた領域を選択
|
||||
|<Plug>(textobj-sandwich-query-i)|, |<Plug>(textobj-sandwich-query-a)|
|
||||
>
|
||||
xmap is <Plug>(textobj-sandwich-query-i)
|
||||
xmap as <Plug>(textobj-sandwich-query-a)
|
||||
omap is <Plug>(textobj-sandwich-query-i)
|
||||
omap as <Plug>(textobj-sandwich-query-a)
|
||||
<
|
||||
* 最も近い括弧に囲まれた領域を選択
|
||||
|<Plug>(textobj-sandwich-auto-i)|, |<Plug>(textobj-sandwich-auto-a)|
|
||||
>
|
||||
xmap iss <Plug>(textobj-sandwich-auto-i)
|
||||
xmap ass <Plug>(textobj-sandwich-auto-a)
|
||||
omap iss <Plug>(textobj-sandwich-auto-i)
|
||||
omap ass <Plug>(textobj-sandwich-auto-a)
|
||||
<
|
||||
* ユーザーの入力に従い、同じ文字に囲まれた領域を選択
|
||||
|<Plug>(textobj-sandwich-literal-query-i)|,
|
||||
|<Plug>(textobj-sandwich-literal-query-a)|
|
||||
>
|
||||
xmap im <Plug>(textobj-sandwich-literal-query-i)
|
||||
xmap am <Plug>(textobj-sandwich-literal-query-a)
|
||||
omap im <Plug>(textobj-sandwich-literal-query-i)
|
||||
omap am <Plug>(textobj-sandwich-literal-query-a)
|
||||
<
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
magicchar-f の挙動を変更する~
|
||||
|
||||
`magicchar-f` は単純な関数呼び出しを削除できます。
|
||||
>
|
||||
func(arg) -- sdf --> arg
|
||||
<
|
||||
もし、もっと複雑なパターンにも対応させたい場合、例えば、
|
||||
>
|
||||
obj.method(arg) -- sdf --> arg
|
||||
<
|
||||
このような場合に対応させるには `g:sandwich#magicchar#f#patterns` か
|
||||
`b:sandwich_magicchar_f_patterns` を使います。 これらは次のようなパターンのリ
|
||||
ストです。
|
||||
>
|
||||
let g:sandwich#magicchar#f#patterns = [
|
||||
\ {
|
||||
\ 'header' : '\<\h\k*',
|
||||
\ 'bra' : '(',
|
||||
\ 'ket' : ')',
|
||||
\ 'footer' : '',
|
||||
\ },
|
||||
\ ]
|
||||
<
|
||||
四つの文字列はすべて正規表現パターンで、開括弧の前の文字列、開括弧、閉括弧、
|
||||
閉括弧の後に続く文字列にマッチします。すなわち、先に述べたようなパターンに対応
|
||||
するには以下のような設定をします。
|
||||
>
|
||||
let g:sandwich#magicchar#f#patterns = [
|
||||
\ {
|
||||
\ 'header' : '\<\%(\h\k*\.\)*\h\k*',
|
||||
\ 'bra' : '(',
|
||||
\ 'ket' : ')',
|
||||
\ 'footer' : '',
|
||||
\ },
|
||||
\ ]
|
||||
<
|
||||
`b:sandwich_magicchar_f_patterns` はファイルタイプ毎の設定を定義するのに使えま
|
||||
す。
|
||||
>
|
||||
augroup sandwich-ft-python
|
||||
autocmd Filetype python let b:sandwich_magicchar_f_patterns = [
|
||||
\ {
|
||||
\ 'header' : '\<\%(\h\k*\.\)*\h\k*',
|
||||
\ 'bra' : '(',
|
||||
\ 'ket' : ')',
|
||||
\ 'footer' : '',
|
||||
\ },
|
||||
\ ]
|
||||
augroup END
|
||||
<
|
||||
デフォルトの設定は `g:sandwich#magicchar#f#default_patterns` にあります。
|
||||
|
||||
==============================================================================
|
||||
vim:tw=78:ts=8:ft=help:norl:noet:
|
||||
699
config/neovim/store/lazy-plugins/vim-sandwich/doc/sandwich.txt
Normal file
699
config/neovim/store/lazy-plugins/vim-sandwich/doc/sandwich.txt
Normal file
@ -0,0 +1,699 @@
|
||||
*sandwich.txt* Last change:03-Jul-2022.
|
||||
|
||||
The set of operator and textobject plugins to edit sandwiched textobjects.
|
||||
|
||||
Author : machakann <mckn{at}outlook.jp>
|
||||
License : NYSL license
|
||||
Japanese <http://www.kmonos.net/nysl/>
|
||||
English (Unofficial) <http://www.kmonos.net/nysl/index.en.html>
|
||||
|
||||
Requirement: Vim 7.4 or higher
|
||||
|+reltime| feature (optional)
|
||||
|+float| feature (optional)
|
||||
|
||||
==============================================================================
|
||||
CONTENTS *sandwich-contents*
|
||||
|
||||
QUICK START |sandwich-quick-start|
|
||||
INTRODUCTION |sandwich-introduction|
|
||||
KEYMAPPINGS |sandwich-keymappings|
|
||||
CONFIGURATION |sandwich-configuration|
|
||||
MAGICCHARACTERS |sandwich-magiccharacters|
|
||||
FILETYPE RECIPES |sandwich-filetype-recipes|
|
||||
FUNCTIONS |sandwich-functions|
|
||||
MISCELLANEOUS |sandwich-miscellaneous|
|
||||
Introduce vim-surround keymappings
|
||||
Customize the behavior of magicchar-f
|
||||
Any way to make a recipe deletes kinds of parentheses and brackets?
|
||||
|
||||
==============================================================================
|
||||
QUICK START *sandwich-quick-start*
|
||||
|
||||
*sandwich.vim* is the set of operator and textobject plugins to
|
||||
add/delete/replace surroundings of a sandwiched textobject, like (foo), "bar".
|
||||
|
||||
add~
|
||||
Press sa{motion/textobject}{addition}.
|
||||
For example, saiw( makes foo to (foo).
|
||||
|
||||
delete~
|
||||
Press sdb or sd{deletion}.
|
||||
For example, sdb or sd( makes (foo) to foo.
|
||||
sdb searchs a set of surrounding automatically.
|
||||
|
||||
replace~
|
||||
Press srb{addition} or sr{deletion}{addition}.
|
||||
For example, srb" or sr(" makes (foo) to "foo".
|
||||
|
||||
Now you already know enough about sandwich.vim. If you want more,
|
||||
read this help and each help documents of operator/textobject,
|
||||
|operator-sandwich| and |textobj-sandwich|.
|
||||
|
||||
|
||||
|
||||
==============================================================================
|
||||
INTRODUCTION *sandwich-introduction*
|
||||
|
||||
This plugin provides functions to add/delete/replace surroundings of
|
||||
sandwiched texts. These functions are implemented genuinely by utilizing
|
||||
operator/textobject framework. Their action can be repeated by |.| command
|
||||
without any dependency.
|
||||
|
||||
Refer to the |sandwich-keymappings| for the key mappings supplied by
|
||||
|sandwich.vim|. It also explains how to change key mappings for your
|
||||
preference.
|
||||
|
||||
Refer to the |operator-sandwich| for the details of the genuine operators.
|
||||
It also explains how to customize the functions to edit surroundings.
|
||||
|
||||
Refer to the |textobj-sandwich| for the details of the genuine textobjects.
|
||||
It also explains how to customize the behavior of textobjects.
|
||||
|
||||
|
||||
|
||||
==============================================================================
|
||||
KEYMAPPINGS *sandwich-keymappings*
|
||||
|
||||
This plugin defines the following keymappings.
|
||||
|
||||
function default keymappings
|
||||
--------------------------------------------------------------------------
|
||||
add sa{motion/textobject}{addition} (normal and visual mode)
|
||||
-> |<Plug>(sandwich-add)|
|
||||
|
||||
delete
|
||||
sd{deletion} (normal mode)
|
||||
sd (visual mode)
|
||||
-> |<Plug>(sandwich-delete)|
|
||||
|
||||
sdb (normal mode)
|
||||
-> |<Plug>(sandwich-delete-auto)|
|
||||
|
||||
replace
|
||||
sr{deletion}{addition} (normal mode)
|
||||
sr{addition} (visual mode)
|
||||
-> |<Plug>(sandwich-replace)|
|
||||
|
||||
srb{addition} (normal mode)
|
||||
-> |<Plug>(sandwich-replace-auto)|
|
||||
|
||||
textobjct
|
||||
ib (operator-pending and visual mode)
|
||||
-> |<Plug>(textobj-sandwich-auto-i)|
|
||||
ab (operator-pending and visual mode)
|
||||
-> |<Plug>(textobj-sandwich-auto-a)|
|
||||
|
||||
is (operator-pending and visual mode)
|
||||
-> |<Plug>(textobj-sandwich-query-i)|
|
||||
as (operator-pending and visual mode)
|
||||
-> |<Plug>(textobj-sandwich-query-a)|
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
NOTE: To prevent unintended operation, the following setting is strongly
|
||||
recommended to add to your vimrc.
|
||||
>
|
||||
nmap s <Nop>
|
||||
xmap s <Nop>
|
||||
<
|
||||
|s| could be easily replaced by |c|l| commands.
|
||||
|
||||
If you don't need the default mappings, define
|
||||
*g:sandwich_no_default_key_mappings* in your vimrc.
|
||||
>
|
||||
let g:sandwich_no_default_key_mappings = 1
|
||||
<
|
||||
The following code snippet shows how to change the trigger key from s to z.
|
||||
>
|
||||
let g:sandwich_no_default_key_mappings = 1
|
||||
|
||||
" add
|
||||
nmap za <Plug>(sandwich-add)
|
||||
xmap za <Plug>(sandwich-add)
|
||||
omap za <Plug>(sandwich-add)
|
||||
|
||||
" delete
|
||||
nmap zd <Plug>(sandwich-delete)
|
||||
xmap zd <Plug>(sandwich-delete)
|
||||
nmap zdb <Plug>(sandwich-delete-auto)
|
||||
|
||||
" replace
|
||||
nmap zr <Plug>(sandwich-replace)
|
||||
xmap zr <Plug>(sandwich-replace)
|
||||
nmap zrb <Plug>(sandwich-replace-auto)
|
||||
<
|
||||
Additionally, map textobjects if you need.
|
||||
>
|
||||
" text-objects (if you need)
|
||||
omap ib <Plug>(textobj-sandwich-auto-i)
|
||||
xmap ib <Plug>(textobj-sandwich-auto-i)
|
||||
omap ab <Plug>(textobj-sandwich-auto-a)
|
||||
xmap ab <Plug>(textobj-sandwich-auto-a)
|
||||
|
||||
omap is <Plug>(textobj-sandwich-query-i)
|
||||
xmap is <Plug>(textobj-sandwich-query-i)
|
||||
omap as <Plug>(textobj-sandwich-query-a)
|
||||
xmap as <Plug>(textobj-sandwich-query-a)
|
||||
<
|
||||
|
||||
|
||||
*<Plug>(sandwich-add)*
|
||||
[count1] <Plug>(sandwich-add) [count2] {motion} {addition}
|
||||
Wrap an assigned text on the buffer.
|
||||
This key mapping handles [count] uniquely.
|
||||
[count1] is given to |<Plug>(sandwich-add)| and thus surround the text
|
||||
[count1] times. On the other hand, [count2] is passed to {motion} as
|
||||
usually. Both of those [count]s are optional. The {addition} is the
|
||||
key to specify the surroundings; for example, an input saiw( wraps a
|
||||
word by parentheses().
|
||||
|
||||
{Visual} [count] <Plug>(sandwich-add) {addition}
|
||||
Wrap the visual-selected text [count] times.
|
||||
|
||||
<Plug>(sandwich-add) is available in normal, visual, and
|
||||
operator-pending mode. It is mapped at sa in default.
|
||||
>
|
||||
nmap sa <Plug>(sandwich-add)
|
||||
xmap sa <Plug>(sandwich-add)
|
||||
omap sa <Plug>(sandwich-add)
|
||||
<
|
||||
|
||||
|
||||
*<Plug>(sandwich-delete)*
|
||||
[count] <Plug>(sandwich-delete) {deletion}
|
||||
Delete a pair of surroundings nearest to the cursor specified by
|
||||
{deletion}. For example, an input sd( deletes a pair of parentheses()
|
||||
nearest to the cursor.
|
||||
>
|
||||
(foo) -> foo
|
||||
<
|
||||
Delete the [count]th closest surroundings if [count] is given.
|
||||
>
|
||||
(foo(bar)baz) cursor is on "bar"
|
||||
|
||||
-- sd( --> (foobarbaz)
|
||||
-- 2sd( --> foo(bar)baz
|
||||
<
|
||||
|
||||
{Visual} [count] <Plug>(sandwich-delete)
|
||||
Delete the successive surroundings at the both ends of the visually
|
||||
selected text. Delete [count] times if [count] is given.
|
||||
|
||||
<Plug>(sandwich-delete) is available in normal and visual mode. It is
|
||||
mapped at sd in default.
|
||||
>
|
||||
nmap sd <Plug>(sandwich-delete)
|
||||
xmap sd <Plug>(sandwich-delete)
|
||||
<
|
||||
|
||||
|
||||
*<Plug>(sandwich-delete-auto)*
|
||||
[count] <Plug>(sandwich-delete-auto)
|
||||
Delete the [count]th closest surroundings from the cursor.
|
||||
>
|
||||
[foo(bar)baz] cursor is on "bar"
|
||||
|
||||
-- sdb --> [foobarbaz]
|
||||
-- 2sdb --> foo(bar)baz
|
||||
<
|
||||
<Plug>(sandwich-delete-auto) is available in normal mode. It is mapped
|
||||
at sdb in default.
|
||||
>
|
||||
nmap sdb <Plug>(sandwich-delete-auto)
|
||||
<
|
||||
|
||||
|
||||
*<Plug>(sandwich-replace)*
|
||||
[count] <Plug>(sandwich-replace) {deletion} {addition}
|
||||
Replace the closest surroundings from the cursor specified by
|
||||
{deletion} to another surroundings specified by {addition}. For
|
||||
example, an input sr([ replaces a pair of parentheses() to a pair of
|
||||
square brackets[].
|
||||
>
|
||||
(foo) -> [foo]
|
||||
<
|
||||
Replace the [count]th closest surroundings if [count] is given.
|
||||
>
|
||||
(foo(bar)baz) cursor is on "bar"
|
||||
|
||||
-- sr([ --> (foo[bar]baz)
|
||||
-- 2sr([ --> [foo(bar)baz]
|
||||
<
|
||||
|
||||
{Visual} [count] <Plug>(sandwich-replace) {addition}
|
||||
Replace the successive surroundings at the both ends of the visually
|
||||
selected text to another surroundings specified by {addition}.
|
||||
Replace [count] times if [count] is given.
|
||||
|
||||
<Plug>(sandwich-replace) is available in normal and visual mode. It is
|
||||
mapped at sr in default.
|
||||
>
|
||||
nmap sr <Plug>(sandwich-replace)
|
||||
xmap sr <Plug>(sandwich-replace)
|
||||
<
|
||||
|
||||
|
||||
*<Plug>(sandwich-replace-auto)*
|
||||
[count] <Plug>(sandwich-replace-auto) {addition}
|
||||
Replace the [count]th closest surroundings from the cursor to another
|
||||
surroundings specified by {addition}.
|
||||
>
|
||||
[foo(bar)baz] cursor is on "bar"
|
||||
|
||||
-- srb{ --> [foo{bar}baz]
|
||||
-- 2srb{ --> {foo(bar)baz}
|
||||
<
|
||||
<Plug>(sandwich-replace-auto) is available in normal mode. It is
|
||||
mapped at srb in default.
|
||||
>
|
||||
nmap srb <Plug>(sandwich-replace-auto)
|
||||
<
|
||||
|
||||
|
||||
==============================================================================
|
||||
CONFIGURATION *sandwich-configuration*
|
||||
|
||||
A set of surroundings and options for it is called "recipe". Each recipe is a
|
||||
dictionary and the |list|s of recipes determines the operator's behavior and
|
||||
textobject's behavior. |g:sandwich#default_recipes| is one of the |list|s of
|
||||
recipes. This is shared to be used by |operator-sandwich| and
|
||||
|textobj-sandwich| since it is convenient in many cases. If
|
||||
|g:sandwich#recipes| is defined by user, it is employed alternatively. The
|
||||
default recipes |g:sandwich#default_recipes| can be checked by |:echo|
|
||||
command.
|
||||
>
|
||||
:echo g:sandwich#default_recipes
|
||||
<
|
||||
Besides them, |g:operator#sandwich#recipes| and |g:textobj#sandwich#recipes|
|
||||
can be used. They are used only by |operator-sandwich| and |textobj-sandwich|
|
||||
respectively.
|
||||
|
||||
About the contents of a recipe, please see |operator-sandwich-configuration|
|
||||
and |textobj-sandwich-configuration|.
|
||||
|
||||
|
||||
|
||||
g:sandwich#recipes *g:sandwich#recipes*
|
||||
This is one of the lists of recipes which is referred from both
|
||||
|operator-sandwich| and |textobj-sandwich|. If this list does not
|
||||
exist, |g:sandwich#default_recipes| is used.
|
||||
*b:sandwich_recipes*
|
||||
If |b:sandwich_recipes| exists, it would be used instead of
|
||||
|g:sandwich#recipes|. This is buffer local, thus it might be
|
||||
convenient to manage too many filetype-specific recipes.
|
||||
|
||||
|
||||
|
||||
g:sandwich#default_recipes *g:sandwich#default_recipes*
|
||||
This is a list of recipes which is prepared in default. If
|
||||
|g:sandwich#recipes| exists, it will be used instead.
|
||||
|
||||
This variable is locked usually, but it can be copied when you declare
|
||||
|g:sandwich#recipes| if you need.
|
||||
>
|
||||
:let g:sandwich#recipes = deepcopy(g:sandwich#default_recipes)
|
||||
<
|
||||
|
||||
Notes on default recipes~
|
||||
`(`, `)`
|
||||
`[`, `]`
|
||||
`{`, `}`
|
||||
`<`, `>`
|
||||
Both open/close braces behave as same. For example, `saiw(` and `saiw)`
|
||||
result in the same text.
|
||||
>
|
||||
foo -> (foo)
|
||||
<
|
||||
`sd(` and `sd)` do the opposite.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
`'`
|
||||
`"`
|
||||
Wrap a text by quotes.
|
||||
>
|
||||
foo -> 'foo'
|
||||
<
|
||||
When deleting a pair of quotes, 'quoteescape' option is considered.
|
||||
The pair of quotes are searched only in a same line.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
`<Space>`
|
||||
When deleting a pair of spaces, successive spaces are deleted at a
|
||||
time.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
`t`, `T`
|
||||
`f`, `F`
|
||||
`i`, `I`
|
||||
See |sandwich-magiccharacters|.
|
||||
|
||||
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
Global options~
|
||||
g:sandwich#timeout *g:sandwich#timeout*
|
||||
If this option is a falsy value, the operators and the
|
||||
query-textobject will wait for subsequent inputs until the complete
|
||||
key sequence has been received to specify a recipe. For example, with
|
||||
the following recipes,
|
||||
>
|
||||
let g:sandwich#recipes = [
|
||||
\ {'buns': ['for {', '}'], 'nesting': 1, 'input': ['bf']}
|
||||
\ {'buns': ['if {', '}'], 'nesting': 1, 'input': ['bi']}
|
||||
\ {'buns': ['else {', '}'], 'nesting': 1, 'input': ['be']}
|
||||
\ ]
|
||||
<
|
||||
type `saiwb` and a while later the operator eagerly wrap a word with
|
||||
`b` if this option is true. The operators wait next input until a
|
||||
recipe is specified if this option is false. This option takes effect
|
||||
both on the operators and the query-textobject.
|
||||
|g:operator#sandwich#timeout| or |g:textobj#sandwich#timeout| takes
|
||||
priority over this option if it exists. If this has not been defined,
|
||||
'timeout' option is referred. See |g:sandwich#timeoutlen| also.
|
||||
|
||||
|
||||
|
||||
g:sandwich#timeoutlen *g:sandwich#timeoutlen*
|
||||
The time in milli seconds that waits for a key code or mapped key
|
||||
sequence to complete. If there are recipes overlapped, this option is
|
||||
used. Assume that the following recipes are prepared:
|
||||
>
|
||||
let g:sandwich#recipes = [
|
||||
\ {'buns': ['(', ')']}
|
||||
\ {'buns': ['((', '))']}
|
||||
\ ]
|
||||
<
|
||||
after pressing saiw(, the operator waits in the time. If you press one
|
||||
more ( in the time, then a recipe for '((' and '))' is decided to use.
|
||||
No keypress has come through the time a recipe for '(' and ')' is
|
||||
settled. This option takes effect both on the operators and the
|
||||
query-textobject. |g:operator#sandwich#timeoutlen| or
|
||||
|g:textobj#sandwich#timeoutlen| takes priority over this option if it
|
||||
exists. If this variable has not been defined, 'timeoutlen' option is
|
||||
referred.
|
||||
|
||||
When the timeout option (|g:operator#sandwich#timeout|,
|
||||
|g:textobj#sandwich#timeout|, |g:sandwich#timeout|, 'timeout') is off,
|
||||
this option is ignored.
|
||||
|
||||
|
||||
|
||||
g:sandwich#input_fallback *g:sandwich#input_fallback*
|
||||
This bool option controls the behavior when no recipe matches with
|
||||
user input to add/delete/replace surroundings. If this option is true
|
||||
and no recipe matches with user input, the user input character itself
|
||||
is used as the surroundings for add/delete/replace. For example, even
|
||||
if there is no recipe associated with input `a` the key sequence
|
||||
`saiwa` wraps a word with `a`.
|
||||
>
|
||||
foo -> afooa
|
||||
<
|
||||
Set falthy value to this option if this behavior is not desired.
|
||||
>
|
||||
let g:sandwich#input_fallback = 0
|
||||
<
|
||||
|
||||
|
||||
|
||||
==============================================================================
|
||||
MAGICCHARACTERS *sandwich-magiccharacters*
|
||||
|
||||
Sandwich.vim requests user to input keys for determination of
|
||||
{addtion}/{deletion}. Usually it is something like `(` for `()` pair or
|
||||
`"` for `""` pair, but there is several functional inputs for cumbersome
|
||||
editings. It might be helpful for your work, give it a try!
|
||||
|
||||
f~
|
||||
F~
|
||||
Press `saiwf` to surround a word by function. After inputting `f` key,
|
||||
user would be requested to input function name and press <CR>, then
|
||||
the target textobject would be surrounded by parenthesis with the
|
||||
function name. <Tab> key completes function names from the current
|
||||
buffer in input.
|
||||
>
|
||||
arg -- saiwffunc<CR> --> func(arg)
|
||||
<
|
||||
The key sequence `sdf`, conversely, deletes function surrounding.
|
||||
>
|
||||
func(arg) -- sdf --> arg
|
||||
<
|
||||
In case of nested functions, `sdf` deletes the function under the
|
||||
cursor while `sdF` deletes the function surrounding.
|
||||
>
|
||||
cursor is on 'func2':
|
||||
func1(func2(arg)) -- sdf --> func1(arg)
|
||||
-- sdF --> func2(arg)
|
||||
<
|
||||
|
||||
i~
|
||||
I~
|
||||
It realizes to define `I`nstant surroundings. `saiwi` ask user for
|
||||
inputting former and latter surroundings. For example,
|
||||
`saiwifoo<CR>bar<CR>` makes a word surrounded by `foo` and `bar`.
|
||||
<Tab> key completes words from the current buffer, just simply.
|
||||
On the other hand `sdi` deletes arbitrary surroundings. For example,
|
||||
`sdifoo<CR>bar<CR>` makes `foowordbar` to `word`, the inputs would be
|
||||
interpreted as regular expressions. This is useful when a lot of
|
||||
targets are there because the action could be repeated by |.| command.
|
||||
`sa{textobj}I`, `sdI` reuse the last inputs.
|
||||
|
||||
|
||||
t~
|
||||
T~
|
||||
The inputs `t` and `T` support to edit HTML style tags. `saiwt` ask
|
||||
user to input a name of element, then a textobject would be surrounded
|
||||
by the tag. `saiwT` works as same.
|
||||
>
|
||||
word -- saiwtp<CR> --> <p>word</p>
|
||||
<
|
||||
`sdt` deletes the nearest tag surroundings. `sdT` works as same.
|
||||
>
|
||||
<p>word</p> -- sdt --> word
|
||||
<
|
||||
`t` and `T` works differently only when replacing surroundings.
|
||||
`srtt` replaces only the name of element, does not touch attributes.
|
||||
`srTT` replaces the whole body of tags.
|
||||
|
||||
|
||||
|
||||
|
||||
==============================================================================
|
||||
FILETYPE RECIPES *sandwich-filetype-recipes*
|
||||
|
||||
Sandwich.vim has filetype specific settings. They will be available when
|
||||
'filetype' option was set to a certain value. User settings are not
|
||||
overwrittern by them, use only the recipes helpful for you. These recipes are
|
||||
written in ftplugin/{filetype}/sandwich.vim.
|
||||
|
||||
If you don't want vim to load these files, set
|
||||
`g:sandwich_no_{filetype}_ftplugin` as true in advance. For example, add the
|
||||
following line to your vimrc in case you don't need tex specific recipes.
|
||||
>
|
||||
let g:sandwich_no_tex_ftplugin = 1
|
||||
<
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
plaintex~
|
||||
tex~
|
||||
>
|
||||
Surroundings Input
|
||||
“{text}” u"
|
||||
„{text}“ U"
|
||||
ug
|
||||
u,
|
||||
«{text}» u<
|
||||
uf
|
||||
`{text}' l'
|
||||
l`
|
||||
``{text}'' l"
|
||||
"`{text}"' L"
|
||||
,,{text}`` l,
|
||||
<<{text}>> l<
|
||||
\{{text}\} \{
|
||||
\[{text}\] \[
|
||||
|
||||
\left({text}\right) m(
|
||||
\left[{text}\right] m[
|
||||
\left|{text}\right| m|
|
||||
\left\{{text}\right\} m{
|
||||
\left\langle {text}\right\rangle m<
|
||||
< The surroundings its input starting from 'm' could be removed/replaced
|
||||
by a input `ma`, for example press `sdma`.
|
||||
>
|
||||
\big({text}\big) M(
|
||||
\big[{text}\big] M[
|
||||
\big|{text}\big| M|
|
||||
\big\{{text}\big\} M{
|
||||
\big\langle {text}\big\rangle M<
|
||||
|
||||
\begingroup{text}\endgroup gr
|
||||
\gr
|
||||
\toprule{text}\bottomrule tr
|
||||
br
|
||||
\tr
|
||||
\br
|
||||
|
||||
\{input}{{text}} c
|
||||
< This recipe asks user to input a string and then {input} is
|
||||
substituted by the input string.
|
||||
>
|
||||
\begin{{input}}{text}\end{{input}} e
|
||||
< This recipe asks user to input a string and then {input} is
|
||||
substituted by the input string.
|
||||
Use <Tab> to complete {input}, the completion items are loaded from
|
||||
`g:sandwich#filetype#tex#environments`
|
||||
(or `b:sandwich#filetype#tex#environments` if exists).
|
||||
|
||||
|
||||
==============================================================================
|
||||
FUNCTIONS *sandwich-functions*
|
||||
|
||||
sandwich#util#addlocal({recipes}) *sandwich#util#addlocal()*
|
||||
This function appends the list of recipes {recipes} as buffer-local
|
||||
settings with inheritance of the global settings. This is useful when
|
||||
one wants to add filetype specific settings with keeping global
|
||||
setting |g:sandwich#recipes| clean. Note that {recipe} is a |List| of
|
||||
recipes.
|
||||
>
|
||||
autocmd FileType python call sandwich#util#addlocal([
|
||||
\ {'buns': ['"""', '"""'], 'nesting': 0, 'input': ['3"']},
|
||||
\ ])
|
||||
<
|
||||
|
||||
==============================================================================
|
||||
MISCELLANEOUS *sandwich-miscellaneous*
|
||||
|
||||
Introduce vim-surround keymappings~
|
||||
If you want to use with vim-surround (vim script #1697) keymappings,
|
||||
add the following line to your vimrc.
|
||||
>
|
||||
runtime macros/sandwich/keymap/surround.vim
|
||||
<
|
||||
NOTE: Unlike surround.vim, the inputs `(` and `)` behave as same.
|
||||
If you want the spaces inside braces with `(` input, add the
|
||||
lines.
|
||||
>
|
||||
runtime macros/sandwich/keymap/surround.vim
|
||||
let g:sandwich#recipes += [
|
||||
\ {'buns': ['{ ', ' }'], 'nesting': 1, 'match_syntax': 1,
|
||||
\ 'kind': ['add', 'replace'], 'action': ['add'], 'input': ['{']},
|
||||
\
|
||||
\ {'buns': ['[ ', ' ]'], 'nesting': 1, 'match_syntax': 1,
|
||||
\ 'kind': ['add', 'replace'], 'action': ['add'], 'input': ['[']},
|
||||
\
|
||||
\ {'buns': ['( ', ' )'], 'nesting': 1, 'match_syntax': 1,
|
||||
\ 'kind': ['add', 'replace'], 'action': ['add'], 'input': ['(']},
|
||||
\
|
||||
\ {'buns': ['{\s*', '\s*}'], 'nesting': 1, 'regex': 1,
|
||||
\ 'match_syntax': 1, 'kind': ['delete', 'replace', 'textobj'],
|
||||
\ 'action': ['delete'], 'input': ['{']},
|
||||
\
|
||||
\ {'buns': ['\[\s*', '\s*\]'], 'nesting': 1, 'regex': 1,
|
||||
\ 'match_syntax': 1, 'kind': ['delete', 'replace', 'textobj'],
|
||||
\ 'action': ['delete'], 'input': ['[']},
|
||||
\
|
||||
\ {'buns': ['(\s*', '\s*)'], 'nesting': 1, 'regex': 1,
|
||||
\ 'match_syntax': 1, 'kind': ['delete', 'replace', 'textobj'],
|
||||
\ 'action': ['delete'], 'input': ['(']},
|
||||
\ ]
|
||||
<
|
||||
|
||||
|
||||
`ys`, `yss`, `yS`, `ds`, `cs` in normal mode and `S` in visual mode
|
||||
are available. Not in vim-surround but `dss` and `css` are also
|
||||
available, these are similar as `ds` and `cs` but determine
|
||||
deleted/replaced texts automatically. See the file directly for
|
||||
detail.
|
||||
|
||||
Additionally, vim-sandwich provides several textobjects. They would
|
||||
also be helpful, give it a try!
|
||||
|
||||
* Textobjects to select a text surrounded by braket or same characters
|
||||
user input.
|
||||
|<Plug>(textobj-sandwich-query-i)|, |<Plug>(textobj-sandwich-query-a)|
|
||||
>
|
||||
xmap is <Plug>(textobj-sandwich-query-i)
|
||||
xmap as <Plug>(textobj-sandwich-query-a)
|
||||
omap is <Plug>(textobj-sandwich-query-i)
|
||||
omap as <Plug>(textobj-sandwich-query-a)
|
||||
<
|
||||
* Textobjects to select the nearest surrounded text automatically.
|
||||
|<Plug>(textobj-sandwich-auto-i)|, |<Plug>(textobj-sandwich-auto-a)|.
|
||||
>
|
||||
xmap iss <Plug>(textobj-sandwich-auto-i)
|
||||
xmap ass <Plug>(textobj-sandwich-auto-a)
|
||||
omap iss <Plug>(textobj-sandwich-auto-i)
|
||||
omap ass <Plug>(textobj-sandwich-auto-a)
|
||||
<
|
||||
* Textobjects to select a text surrounded by same characters user
|
||||
input.
|
||||
|<Plug>(textobj-sandwich-literal-query-i)|,
|
||||
|<Plug>(textobj-sandwich-literal-query-a)|
|
||||
>
|
||||
xmap im <Plug>(textobj-sandwich-literal-query-i)
|
||||
xmap am <Plug>(textobj-sandwich-literal-query-a)
|
||||
omap im <Plug>(textobj-sandwich-literal-query-i)
|
||||
omap am <Plug>(textobj-sandwich-literal-query-a)
|
||||
<
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
Customize the behavior of magicchar-f~
|
||||
|
||||
`magicchar-f` can delete simple function-call like syntax:
|
||||
>
|
||||
func(arg) -- sdf --> arg
|
||||
<
|
||||
If you want to delete more advanced patterns, for example:
|
||||
>
|
||||
obj.method(arg) -- sdf --> arg
|
||||
<
|
||||
You can use `g:sandwich#magicchar#f#patterns` or
|
||||
`b:sandwich_magicchar_f_patterns` for the purpose. Each of those are a list of
|
||||
patterns like:
|
||||
>
|
||||
let g:sandwich#magicchar#f#patterns = [
|
||||
\ {
|
||||
\ 'header' : '\<\h\k*',
|
||||
\ 'bra' : '(',
|
||||
\ 'ket' : ')',
|
||||
\ 'footer' : '',
|
||||
\ },
|
||||
\ ]
|
||||
<
|
||||
Those four values are all regex patterns, which match with something before
|
||||
open parenthesis, open & close parentheses, something after close parenthesis.
|
||||
Therefore, you can delete a method with an object by the following setting.
|
||||
>
|
||||
let g:sandwich#magicchar#f#patterns = [
|
||||
\ {
|
||||
\ 'header' : '\<\%(\h\k*\.\)*\h\k*',
|
||||
\ 'bra' : '(',
|
||||
\ 'ket' : ')',
|
||||
\ 'footer' : '',
|
||||
\ },
|
||||
\ ]
|
||||
<
|
||||
`b:sandwich_magicchar_f_patterns` can be used to define filetype specific
|
||||
setting.
|
||||
>
|
||||
augroup sandwich-ft-python
|
||||
autocmd Filetype python let b:sandwich_magicchar_f_patterns = [
|
||||
\ {
|
||||
\ 'header' : '\<\%(\h\k*\.\)*\h\k*',
|
||||
\ 'bra' : '(',
|
||||
\ 'ket' : ')',
|
||||
\ 'footer' : '',
|
||||
\ },
|
||||
\ ]
|
||||
augroup END
|
||||
<
|
||||
The default settings is in `g:sandwich#magicchar#f#default_patterns`.
|
||||
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
Any way to make a recipe deletes kinds of parentheses and brackets?~
|
||||
|
||||
See |sandwich-compound-recipes|.
|
||||
|
||||
==============================================================================
|
||||
vim:tw=78:ts=8:ft=help:norl:noet:
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,176 @@
|
||||
scriptencoding utf-8
|
||||
if !exists('s:local_recipes')
|
||||
let s:local_recipes = [
|
||||
\ {'__filetype__': 'tex', 'buns': ['“', '”'], 'nesting': 1, 'input': [ 'u"' ]},
|
||||
\ {'__filetype__': 'tex', 'buns': ['„', '“'], 'nesting': 1, 'input': [ 'U"', 'ug', 'u,' ]},
|
||||
\ {'__filetype__': 'tex', 'buns': ['«', '»'], 'nesting': 1, 'input': [ 'u<', 'uf' ]},
|
||||
\ {'__filetype__': 'tex', 'buns': ["`", "'"], 'nesting': 1, 'input': [ "l'", "l`" ]},
|
||||
\ {'__filetype__': 'tex', 'buns': ["``", "''"], 'nesting': 1, 'input': [ 'l"' ]},
|
||||
\ {'__filetype__': 'tex', 'buns': ['"`', "\"'"], 'nesting': 1, 'input': [ 'L"' ]},
|
||||
\ {'__filetype__': 'tex', 'buns': [",,", "``"], 'nesting': 1, 'input': [ 'l,' ]},
|
||||
\ {'__filetype__': 'tex', 'buns': ['<<', '>>'], 'nesting': 1, 'input': [ 'l<' ]},
|
||||
\ {'__filetype__': 'tex', 'buns': ['$', '$'], 'nesting': 0},
|
||||
\ {'__filetype__': 'tex', 'buns': ['\(', '\)'], 'nesting': 1, 'input': [ '\(' ], 'indentkeys-': '{,},0{,0}'},
|
||||
\ {'__filetype__': 'tex', 'buns': ['\{', '\}'], 'nesting': 1, 'input': [ '\{' ], 'indentkeys-': '{,},0{,0}'},
|
||||
\ {'__filetype__': 'tex', 'buns': ['\[', '\]'], 'nesting': 1, 'input': [ '\[' ], 'indentkeys-': '{,},0{,0}'},
|
||||
\ {'__filetype__': 'tex', 'buns': ['\left(', '\right)'], 'nesting': 1, 'input': [ 'm(' ], 'action': ['add'], 'indentkeys-': '(,)'},
|
||||
\ {'__filetype__': 'tex', 'buns': ['\left[', '\right]'], 'nesting': 1, 'input': [ 'm[' ], 'action': ['add'], 'indentkeys-': '[,]'},
|
||||
\ {'__filetype__': 'tex', 'buns': ['\left|', '\right|'], 'nesting': 1, 'input': [ 'm|' ], 'action': ['add']},
|
||||
\ {'__filetype__': 'tex', 'buns': ['\left\{', '\right\}'], 'nesting': 1, 'input': [ 'm{' ], 'action': ['add'], 'indentkeys-': '{,},0{,0}'},
|
||||
\ {'__filetype__': 'tex', 'buns': ['\left\langle ', '\right\rangle '], 'nesting': 1, 'input': [ 'm<' ], 'action': ['add']},
|
||||
\ {'__filetype__': 'tex', 'buns': ['\bigl(', '\bigr)'], 'nesting': 1, 'input': [ 'M(' ], 'action': ['add'], 'indentkeys-': '(,)'},
|
||||
\ {'__filetype__': 'tex', 'buns': ['\bigl[', '\bigr]'], 'nesting': 1, 'input': [ 'M[' ], 'action': ['add'], 'indentkeys-': '[,]'},
|
||||
\ {'__filetype__': 'tex', 'buns': ['\bigl|', '\bigr|'], 'nesting': 1, 'input': [ 'M|' ], 'action': ['add']},
|
||||
\ {'__filetype__': 'tex', 'buns': ['\bigl\{', '\bigr\}'], 'nesting': 1, 'input': [ 'M{' ], 'action': ['add'], 'indentkeys-': '{,},0{,0}'},
|
||||
\ {'__filetype__': 'tex', 'buns': ['\bigl\langle ', '\bigr\rangle '], 'nesting': 1, 'input': [ 'M<' ], 'action': ['add']},
|
||||
\ {
|
||||
\ '__filetype__': 'tex',
|
||||
\ 'buns' : ['\begingroup', '\endgroup'],
|
||||
\ 'nesting' : 1,
|
||||
\ 'input': ['gr', '\gr'],
|
||||
\ 'linewise': 1,
|
||||
\ },
|
||||
\ {
|
||||
\ '__filetype__': 'tex',
|
||||
\ 'buns' : ['\toprule', '\bottomrule'],
|
||||
\ 'nesting' : 1,
|
||||
\ 'input': ['tr', '\tr', 'br', '\br'],
|
||||
\ 'linewise': 1,
|
||||
\ },
|
||||
\ {
|
||||
\ '__filetype__': 'tex',
|
||||
\ 'buns' : 'sandwich#filetype#tex#CmdInput()',
|
||||
\ 'kind' : ['add', 'replace'],
|
||||
\ 'action' : ['add'],
|
||||
\ 'listexpr': 1,
|
||||
\ 'nesting' : 1,
|
||||
\ 'input' : ['c'],
|
||||
\ 'indentkeys-' : '{,},0{,0}',
|
||||
\ },
|
||||
\ {
|
||||
\ '__filetype__': 'tex',
|
||||
\ 'buns' : 'sandwich#filetype#tex#EnvInput()',
|
||||
\ 'kind' : ['add', 'replace'],
|
||||
\ 'action' : ['add'],
|
||||
\ 'listexpr': 1,
|
||||
\ 'nesting' : 1,
|
||||
\ 'linewise' : 1,
|
||||
\ 'input' : ['e'],
|
||||
\ 'indentkeys-' : '{,},0{,0}',
|
||||
\ 'autoindent' : 0,
|
||||
\ },
|
||||
\ {
|
||||
\ '__filetype__': 'tex',
|
||||
\ 'buns' : ['\\\a\+\*\?{', '}'],
|
||||
\ 'kind' : ['delete', 'replace', 'auto', 'query'],
|
||||
\ 'regex' : 1,
|
||||
\ 'nesting' : 1,
|
||||
\ 'input' : ['c'],
|
||||
\ 'indentkeys-' : '{,},0{,0}',
|
||||
\ },
|
||||
\ {
|
||||
\ '__filetype__': 'tex',
|
||||
\ 'buns' : ['\\begin{[^}]*}\%(\[.*\]\)\?', '\\end{[^}]*}'],
|
||||
\ 'kind' : ['delete', 'replace', 'auto', 'query'],
|
||||
\ 'regex' : 1,
|
||||
\ 'nesting' : 1,
|
||||
\ 'linewise' : 1,
|
||||
\ 'input' : ['e'],
|
||||
\ 'indentkeys-' : '{,},0{,0}',
|
||||
\ 'autoindent' : 0,
|
||||
\ },
|
||||
\ {
|
||||
\ '__filetype__': 'tex',
|
||||
\ 'external': ["\<Plug>(textobj-sandwich-filetype-tex-marks-i)", "\<Plug>(textobj-sandwich-filetype-tex-marks-a)"],
|
||||
\ 'kind' : ['delete', 'replace', 'auto', 'query'],
|
||||
\ 'noremap' : 0,
|
||||
\ 'input' : ['ma'],
|
||||
\ 'indentkeys': '{,},0{,0}',
|
||||
\ 'autoindent': 0,
|
||||
\ },
|
||||
\ ]
|
||||
|
||||
xnoremap <silent><expr> <Plug>(textobj-sandwich-filetype-tex-marks-i) textobj#sandwich#auto('x', 'i', {'synchro': 0}, b:sandwich_tex_marks_recipes)
|
||||
xnoremap <silent><expr> <Plug>(textobj-sandwich-filetype-tex-marks-a) textobj#sandwich#auto('x', 'a', {'synchro': 0}, b:sandwich_tex_marks_recipes)
|
||||
let s:marks_recipes = []
|
||||
let s:marks_recipes += [
|
||||
\ {
|
||||
\ 'buns': ['\%([[(]\|\\{\)', '\%([])]\|\\}\)'],
|
||||
\ 'regex': 1,
|
||||
\ 'nesting': 1,
|
||||
\ },
|
||||
\ {
|
||||
\ 'buns': ['|', '|'],
|
||||
\ 'nesting': 0,
|
||||
\ },
|
||||
\ {
|
||||
\ 'buns': ['\m\C\\[Bb]igg\?l|', '\m\C\\[Bb]igg\?r|'],
|
||||
\ 'regex': 1,
|
||||
\ 'nesting': 1,
|
||||
\ },
|
||||
\ {
|
||||
\ 'buns': ['\m\C\\\%(langle\|lVert\|lvert\|lceil\|lfloor\)', '\m\C\\\%(rangle\|rVert\|rvert\|rceil\|rfloor\)'],
|
||||
\ 'regex': 1,
|
||||
\ 'nesting': 1,
|
||||
\ },
|
||||
\ {
|
||||
\ 'buns': ['\m\C\\left\%([[(|.]\|\\{\|\\langle\|\\lVert\|\\lvert\|\\lceil\|\\lfloor\)', '\m\C\\right\%([])|.]\|\\}\|\\rangle\|\\rVert\|\\rvert\|\\rceil\|\\rfloor\)'],
|
||||
\ 'regex': 1,
|
||||
\ 'nesting': 1,
|
||||
\ },
|
||||
\ ]
|
||||
" NOTE: It is not reasonable to set 'nesting' on when former and latter surrounds are same.
|
||||
let s:marks_recipes += [
|
||||
\ {
|
||||
\ 'buns': ['\m\C\\[Bb]igg\?|', '\m\C\\[Bb]igg\?|'],
|
||||
\ 'regex': 1,
|
||||
\ 'nesting': 0,
|
||||
\ },
|
||||
\ ]
|
||||
" NOTE: The existence of '\big.' makes the situation tricky.
|
||||
" Try to search those two cases independently and adopt the nearest item.
|
||||
" \big. foo \big)
|
||||
" \big( foo \big.
|
||||
" This roundabout enables the following:
|
||||
" \big( foo \big. bar \big. baz \big)
|
||||
" When the cursor is on;
|
||||
" foo -> \big( and \big.
|
||||
" bar -> nothing
|
||||
" foo -> \big. and \big)
|
||||
" were deleted by the input 'sdma'.
|
||||
let s:marks_recipes += [
|
||||
\ {
|
||||
\ 'buns': ['\m\C\\[Bb]igg\?l\?\%([[(]\|\\{\|\\langle\|\\lVert\|\\lvert\|\\lceil\|\\lfloor\)', '\m\C\\[Bb]igg\?r\?\%([]).]\|\\}\|\\rangle\|\\rVert\|\\rvert\|\\rceil\|\\rfloor\)'],
|
||||
\ 'regex': 1,
|
||||
\ 'nesting': 1,
|
||||
\ },
|
||||
\ {
|
||||
\ 'buns': ['\m\C\\[Bb]igg\?l\?\%([[(.]\|\\{\|\\langle\|\\lVert\|\\lvert\|\\lceil\|\\lfloor\)', '\m\C\\[Bb]igg\?r\?\%([])]\|\\}\|\\rangle\|\\rVert\|\\rvert\|\\rceil\|\\rfloor\)'],
|
||||
\ 'regex': 1,
|
||||
\ 'nesting': 1,
|
||||
\ },
|
||||
\ {
|
||||
\ 'buns': ['\m\C\\[Bb]igg\?|', '\m\C\\[Bb]igg\?.'],
|
||||
\ 'regex': 1,
|
||||
\ 'nesting': 0,
|
||||
\ },
|
||||
\ {
|
||||
\ 'buns': ['\m\C\\[Bb]igg\?.', '\m\C\\[Bb]igg\?|'],
|
||||
\ 'regex': 1,
|
||||
\ 'nesting': 0,
|
||||
\ },
|
||||
\ {
|
||||
\ 'buns': ['\m\C\\[Bb]igg\?l|', '\m\C\\[Bb]igg\?r[|.]'],
|
||||
\ 'regex': 1,
|
||||
\ 'nesting': 1,
|
||||
\ },
|
||||
\ {
|
||||
\ 'buns': ['\m\C\\[Bb]igg\?l[|.]', '\m\C\\[Bb]igg\?r|'],
|
||||
\ 'regex': 1,
|
||||
\ 'nesting': 1,
|
||||
\ },
|
||||
\ ]
|
||||
endif
|
||||
call sandwich#util#insertlocal(s:local_recipes)
|
||||
let b:sandwich_tex_marks_recipes = deepcopy(s:marks_recipes)
|
||||
|
||||
@ -0,0 +1,200 @@
|
||||
let g:sandwich_no_default_key_mappings = 1
|
||||
let g:operator_sandwich_no_default_key_mappings = 1
|
||||
let g:textobj_sandwich_no_default_key_mappings = 1
|
||||
|
||||
nmap ys <Plug>(sandwich-add)
|
||||
onoremap <SID>line :normal! ^vg_<CR>
|
||||
nmap <silent> yss <Plug>(sandwich-add)<SID>line
|
||||
onoremap <SID>gul g_
|
||||
nmap <silent> yS <Plug>(sandwich-add)<SID>gul
|
||||
|
||||
nmap ds <Plug>(sandwich-delete)
|
||||
nmap dss <Plug>(sandwich-delete-auto)
|
||||
nmap cs <Plug>(sandwich-replace)
|
||||
nmap css <Plug>(sandwich-replace-auto)
|
||||
|
||||
xmap S <Plug>(sandwich-add)
|
||||
|
||||
runtime autoload/repeat.vim
|
||||
if hasmapto('<Plug>(RepeatDot)')
|
||||
nmap . <Plug>(operator-sandwich-predot)<Plug>(RepeatDot)
|
||||
else
|
||||
nmap . <Plug>(operator-sandwich-dot)
|
||||
endif
|
||||
|
||||
" Default recipes
|
||||
let g:sandwich#recipes = [
|
||||
\ {
|
||||
\ 'buns': ['\s\+', '\s\+'],
|
||||
\ 'regex': 1,
|
||||
\ 'kind': ['delete', 'replace', 'query'],
|
||||
\ 'input': [' ']
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'buns': ['', ''],
|
||||
\ 'action': ['add'],
|
||||
\ 'motionwise': ['line'],
|
||||
\ 'linewise': 1,
|
||||
\ 'input': ["\<CR>"]
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'buns': ['^$', '^$'],
|
||||
\ 'regex': 1,
|
||||
\ 'linewise': 1,
|
||||
\ 'input': ["\<CR>"]
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'buns': ['<', '>'],
|
||||
\ 'expand_range': 0,
|
||||
\ 'input': ['>', 'a'],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'buns': ['`', '`'],
|
||||
\ 'quoteescape': 1,
|
||||
\ 'expand_range': 0,
|
||||
\ 'nesting': 0,
|
||||
\ 'linewise': 0,
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'buns': ['"', '"'],
|
||||
\ 'quoteescape': 1,
|
||||
\ 'expand_range': 0,
|
||||
\ 'nesting': 0,
|
||||
\ 'linewise': 0,
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'buns': ["'", "'"],
|
||||
\ 'quoteescape': 1,
|
||||
\ 'expand_range': 0,
|
||||
\ 'nesting': 0,
|
||||
\ 'linewise': 0,
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'buns': ['{', '}'],
|
||||
\ 'nesting': 1,
|
||||
\ 'skip_break': 1,
|
||||
\ 'input': ['{', '}', 'B'],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'buns': ['[', ']'],
|
||||
\ 'nesting': 1,
|
||||
\ 'input': ['[', ']', 'r'],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'buns': ['(', ')'],
|
||||
\ 'nesting': 1,
|
||||
\ 'input': ['(', ')', 'b'],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'buns': 'sandwich#magicchar#t#tag()',
|
||||
\ 'listexpr': 1,
|
||||
\ 'kind': ['add'],
|
||||
\ 'action': ['add'],
|
||||
\ 'input': ['t', 'T'],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'buns': 'sandwich#magicchar#t#tag()',
|
||||
\ 'listexpr': 1,
|
||||
\ 'kind': ['replace'],
|
||||
\ 'action': ['add'],
|
||||
\ 'input': ['T', '<'],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'buns': 'sandwich#magicchar#t#tagname()',
|
||||
\ 'listexpr': 1,
|
||||
\ 'kind': ['replace'],
|
||||
\ 'action': ['add'],
|
||||
\ 'input': ['t'],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'external': ["\<Plug>(textobj-sandwich-tag-i)", "\<Plug>(textobj-sandwich-tag-a)"],
|
||||
\ 'noremap': 0,
|
||||
\ 'kind': ['delete', 'textobj'],
|
||||
\ 'expr_filter': ['operator#sandwich#kind() !=# "replace"'],
|
||||
\ 'linewise': 1,
|
||||
\ 'input': ['t', 'T', '<'],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'external': ["\<Plug>(textobj-sandwich-tag-i)", "\<Plug>(textobj-sandwich-tag-a)"],
|
||||
\ 'noremap': 0,
|
||||
\ 'kind': ['replace', 'query'],
|
||||
\ 'expr_filter': ['operator#sandwich#kind() ==# "replace"'],
|
||||
\ 'input': ['T', '<'],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'external': ["\<Plug>(textobj-sandwich-tagname-i)", "\<Plug>(textobj-sandwich-tagname-a)"],
|
||||
\ 'noremap': 0,
|
||||
\ 'kind': ['replace', 'textobj'],
|
||||
\ 'expr_filter': ['operator#sandwich#kind() ==# "replace"'],
|
||||
\ 'input': ['t'],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'buns': ['sandwich#magicchar#f#fname()', '")"'],
|
||||
\ 'kind': ['add', 'replace'],
|
||||
\ 'action': ['add'],
|
||||
\ 'expr': 1,
|
||||
\ 'input': ['f']
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'external': ["\<Plug>(textobj-sandwich-function-ip)", "\<Plug>(textobj-sandwich-function-i)"],
|
||||
\ 'noremap': 0,
|
||||
\ 'kind': ['delete', 'replace', 'query'],
|
||||
\ 'input': ['f']
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'external': ["\<Plug>(textobj-sandwich-function-ap)", "\<Plug>(textobj-sandwich-function-a)"],
|
||||
\ 'noremap': 0,
|
||||
\ 'kind': ['delete', 'replace', 'query'],
|
||||
\ 'input': ['F']
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'buns': 'sandwich#magicchar#i#input("operator")',
|
||||
\ 'kind': ['add', 'replace'],
|
||||
\ 'action': ['add'],
|
||||
\ 'listexpr': 1,
|
||||
\ 'input': ['i'],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'buns': 'sandwich#magicchar#i#input("textobj", 1)',
|
||||
\ 'kind': ['delete', 'replace', 'query'],
|
||||
\ 'listexpr': 1,
|
||||
\ 'regex': 1,
|
||||
\ 'input': ['i'],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'buns': 'sandwich#magicchar#i#lastinput("operator", 1)',
|
||||
\ 'kind': ['add', 'replace'],
|
||||
\ 'action': ['add'],
|
||||
\ 'listexpr': 1,
|
||||
\ 'input': ['I'],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'buns': 'sandwich#magicchar#i#lastinput("textobj")',
|
||||
\ 'kind': ['delete', 'replace', 'query'],
|
||||
\ 'listexpr': 1,
|
||||
\ 'regex': 1,
|
||||
\ 'input': ['I'],
|
||||
\ },
|
||||
\ ]
|
||||
@ -0,0 +1,60 @@
|
||||
" The vim operator plugin to do well with 'sandwich' like structure
|
||||
" Last Change: 31-Oct-2021.
|
||||
" Maintainer : Masaaki Nakamura <mckn@outlook.jp>
|
||||
|
||||
" License : NYSL
|
||||
" Japanese <http://www.kmonos.net/nysl/>
|
||||
" English (Unofficial) <http://www.kmonos.net/nysl/index.en.html>
|
||||
|
||||
if &compatible || exists("g:loaded_operator_sandwich")
|
||||
finish
|
||||
endif
|
||||
let g:loaded_operator_sandwich = 1
|
||||
|
||||
" keymappings
|
||||
nmap <silent> <Plug>(operator-sandwich-add) <Plug>(operator-sandwich-add-pre)<Plug>(operator-sandwich-g@)
|
||||
xmap <silent> <Plug>(operator-sandwich-add) <Plug>(operator-sandwich-add-pre)<Plug>(operator-sandwich-gv)<Plug>(operator-sandwich-g@)
|
||||
omap <silent> <Plug>(operator-sandwich-add) <Plug>(operator-sandwich-g@)
|
||||
nmap <silent> <Plug>(operator-sandwich-delete) <Plug>(operator-sandwich-delete-pre)<Plug>(operator-sandwich-g@)
|
||||
xmap <silent> <Plug>(operator-sandwich-delete) <Plug>(operator-sandwich-delete-pre)<Plug>(operator-sandwich-gv)<Plug>(operator-sandwich-g@)
|
||||
omap <silent> <Plug>(operator-sandwich-delete) <Plug>(operator-sandwich-g@)
|
||||
nmap <silent> <Plug>(operator-sandwich-replace) <Plug>(operator-sandwich-replace-pre)<Plug>(operator-sandwich-g@)
|
||||
xmap <silent> <Plug>(operator-sandwich-replace) <Plug>(operator-sandwich-replace-pre)<Plug>(operator-sandwich-gv)<Plug>(operator-sandwich-g@)
|
||||
omap <silent> <Plug>(operator-sandwich-replace) <Plug>(operator-sandwich-g@)
|
||||
|
||||
nnoremap <silent> <Plug>(operator-sandwich-add-pre) :<C-u>call operator#sandwich#prerequisite('add', 'n')<CR>
|
||||
xnoremap <silent> <Plug>(operator-sandwich-add-pre) <Esc>:call operator#sandwich#prerequisite('add', 'x')<CR>
|
||||
nnoremap <silent> <Plug>(operator-sandwich-delete-pre) :<C-u>call operator#sandwich#prerequisite('delete', 'n')<CR>
|
||||
xnoremap <silent> <Plug>(operator-sandwich-delete-pre) <Esc>:call operator#sandwich#prerequisite('delete', 'x')<CR>
|
||||
nnoremap <silent> <Plug>(operator-sandwich-replace-pre) :<C-u>call operator#sandwich#prerequisite('replace', 'n')<CR>
|
||||
xnoremap <silent> <Plug>(operator-sandwich-replace-pre) <Esc>:call operator#sandwich#prerequisite('replace', 'x')<CR>
|
||||
|
||||
nnoremap <silent> <Plug>(operator-sandwich-add-query1st) :<C-u>call operator#sandwich#query1st('add', 'n')<CR>
|
||||
xnoremap <silent> <Plug>(operator-sandwich-add-query1st) <Esc>:call operator#sandwich#query1st('add', 'x')<CR>
|
||||
nnoremap <silent> <Plug>(operator-sandwich-replace-query1st) :<C-u>call operator#sandwich#query1st('replace', 'n')<CR>
|
||||
xnoremap <silent> <Plug>(operator-sandwich-replace-query1st) <Esc>:call operator#sandwich#query1st('replace', 'x')<CR>
|
||||
|
||||
" supplementary keymappings
|
||||
onoremap <expr><silent> <Plug>(operator-sandwich-synchro-count) operator#sandwich#synchro_count()
|
||||
onoremap <expr><silent> <Plug>(operator-sandwich-release-count) operator#sandwich#release_count()
|
||||
onoremap <expr><silent> <Plug>(operator-sandwich-squash-count) operator#sandwich#squash_count()
|
||||
nnoremap <expr><silent> <Plug>(operator-sandwich-predot) operator#sandwich#predot()
|
||||
nnoremap <expr><silent> <Plug>(operator-sandwich-dot) operator#sandwich#dot()
|
||||
|
||||
" visualrepeat.vim (vimscript #3848) support
|
||||
noremap <silent> <Plug>(operator-sandwich-add-visualrepeat) :<C-u>call operator#sandwich#visualrepeat('add')<CR>
|
||||
noremap <silent> <Plug>(operator-sandwich-delete-visualrepeat) :<C-u>call operator#sandwich#visualrepeat('delete')<CR>
|
||||
noremap <silent> <Plug>(operator-sandwich-replace-visualrepeat) :<C-u>call operator#sandwich#visualrepeat('replace')<CR>
|
||||
|
||||
" intrinsic keymappings
|
||||
noremap <Plug>(operator-sandwich-g@) g@
|
||||
inoremap <Plug>(operator-sandwich-g@) <C-o>g@
|
||||
nnoremap <Plug>(operator-sandwich-gv) gv
|
||||
inoremap <Plug>(operator-sandwich-gv) <C-o>gv
|
||||
|
||||
" use of vim-event-DotCommandPre
|
||||
if !hasmapto('<Plug>(operator-sandwich-predot)') && !hasmapto('<Plug>(operator-sandwich-dot)') && (hasmapto('<Plug>(event-DotCommandPre)') || hasmapto('<Plug>(event-DotCommandPre+Dot)'))
|
||||
augroup sandwich-predot
|
||||
autocmd User DotCommandPre call operator#sandwich#predot()
|
||||
augroup END
|
||||
endif
|
||||
@ -0,0 +1,75 @@
|
||||
" The set of operator/textobj plugins to do well with 'sandwich' like structure
|
||||
" Last Change: 18-Jan-2022.
|
||||
" Maintainer : Masaaki Nakamura <mckn@outlook.jp>
|
||||
|
||||
" License : NYSL
|
||||
" Japanese <http://www.kmonos.net/nysl/>
|
||||
" English (Unofficial) <http://www.kmonos.net/nysl/index.en.html>
|
||||
|
||||
if &compatible || exists("g:loaded_sandwich")
|
||||
finish
|
||||
endif
|
||||
let g:loaded_sandwich = 1
|
||||
|
||||
" intrinsic keymappings
|
||||
onoremap <silent> <Plug>(textobj-sandwich-function-ip) :<C-u>call sandwich#magicchar#f#ip('o')<CR>
|
||||
onoremap <silent> <Plug>(textobj-sandwich-function-i) :<C-u>call sandwich#magicchar#f#i('o')<CR>
|
||||
xnoremap <silent> <Plug>(textobj-sandwich-function-ip) :<C-u>call sandwich#magicchar#f#ip('x')<CR>
|
||||
xnoremap <silent> <Plug>(textobj-sandwich-function-i) :<C-u>call sandwich#magicchar#f#i('x')<CR>
|
||||
onoremap <silent> <Plug>(textobj-sandwich-function-ap) :<C-u>call sandwich#magicchar#f#ap('o')<CR>
|
||||
onoremap <silent> <Plug>(textobj-sandwich-function-a) :<C-u>call sandwich#magicchar#f#a('o')<CR>
|
||||
xnoremap <silent> <Plug>(textobj-sandwich-function-ap) :<C-u>call sandwich#magicchar#f#ap('x')<CR>
|
||||
xnoremap <silent> <Plug>(textobj-sandwich-function-a) :<C-u>call sandwich#magicchar#f#a('x')<CR>
|
||||
onoremap <silent> <Plug>(textobj-sandwich-tagname-i) :<C-u>call sandwich#magicchar#t#i()<CR>
|
||||
onoremap <silent> <Plug>(textobj-sandwich-tagname-a) :<C-u>call sandwich#magicchar#t#a()<CR>
|
||||
xnoremap <silent> <Plug>(textobj-sandwich-tagname-i) :<C-u>call sandwich#magicchar#t#i()<CR>
|
||||
xnoremap <silent> <Plug>(textobj-sandwich-tagname-a) :<C-u>call sandwich#magicchar#t#a()<CR>
|
||||
onoremap <silent> <Plug>(textobj-sandwich-tag-i) :<C-u>call sandwich#magicchar#t#it()<CR>
|
||||
onoremap <silent> <Plug>(textobj-sandwich-tag-a) :<C-u>call sandwich#magicchar#t#at()<CR>
|
||||
xnoremap <silent> <Plug>(textobj-sandwich-tag-i) :<C-u>call sandwich#magicchar#t#it()<CR>
|
||||
xnoremap <silent> <Plug>(textobj-sandwich-tag-a) :<C-u>call sandwich#magicchar#t#at()<CR>
|
||||
|
||||
nmap <silent> <Plug>(sandwich-add) <Plug>(operator-sandwich-add)
|
||||
xmap <silent> <Plug>(sandwich-add) <Plug>(operator-sandwich-add)
|
||||
omap <silent> <Plug>(sandwich-add) <Plug>(operator-sandwich-add)
|
||||
nmap <silent> <Plug>(sandwich-delete) <Plug>(operator-sandwich-delete)<Plug>(operator-sandwich-release-count)<Plug>(textobj-sandwich-query-a)
|
||||
xmap <silent> <Plug>(sandwich-delete) <Plug>(operator-sandwich-delete)
|
||||
nmap <silent> <Plug>(sandwich-replace) <Plug>(operator-sandwich-replace)<Plug>(operator-sandwich-release-count)<Plug>(textobj-sandwich-query-a)
|
||||
xmap <silent> <Plug>(sandwich-replace) <Plug>(operator-sandwich-replace)
|
||||
nmap <silent> <Plug>(sandwich-delete-auto) <Plug>(operator-sandwich-delete)<Plug>(operator-sandwich-release-count)<Plug>(textobj-sandwich-auto-a)
|
||||
nmap <silent> <Plug>(sandwich-replace-auto) <Plug>(operator-sandwich-replace)<Plug>(operator-sandwich-release-count)<Plug>(textobj-sandwich-auto-a)
|
||||
|
||||
""" default keymappings
|
||||
" If g:sandwich_no_default_key_mappings has been defined, then quit immediately.
|
||||
if exists('g:sandwich_no_default_key_mappings') | finish | endif
|
||||
|
||||
if !exists('g:operator_sandwich_no_default_key_mappings')
|
||||
" add
|
||||
silent! nmap <unique> sa <Plug>(sandwich-add)
|
||||
silent! xmap <unique> sa <Plug>(sandwich-add)
|
||||
silent! omap <unique> sa <Plug>(sandwich-add)
|
||||
|
||||
" delete
|
||||
silent! nmap <unique> sd <Plug>(sandwich-delete)
|
||||
silent! xmap <unique> sd <Plug>(sandwich-delete)
|
||||
silent! nmap <unique> sdb <Plug>(sandwich-delete-auto)
|
||||
|
||||
" replace
|
||||
silent! nmap <unique> sr <Plug>(sandwich-replace)
|
||||
silent! xmap <unique> sr <Plug>(sandwich-replace)
|
||||
silent! nmap <unique> srb <Plug>(sandwich-replace-auto)
|
||||
endif
|
||||
|
||||
if !exists('g:textobj_sandwich_no_default_key_mappings')
|
||||
" auto
|
||||
silent! omap <unique> ib <Plug>(textobj-sandwich-auto-i)
|
||||
silent! xmap <unique> ib <Plug>(textobj-sandwich-auto-i)
|
||||
silent! omap <unique> ab <Plug>(textobj-sandwich-auto-a)
|
||||
silent! xmap <unique> ab <Plug>(textobj-sandwich-auto-a)
|
||||
|
||||
" query
|
||||
silent! omap <unique> is <Plug>(textobj-sandwich-query-i)
|
||||
silent! xmap <unique> is <Plug>(textobj-sandwich-query-i)
|
||||
silent! omap <unique> as <Plug>(textobj-sandwich-query-a)
|
||||
silent! xmap <unique> as <Plug>(textobj-sandwich-query-a)
|
||||
endif
|
||||
@ -0,0 +1,33 @@
|
||||
" The vim textobject plugin to search and select 'sandwich' like structure
|
||||
" Last Change: 30-Oct-2021.
|
||||
" Maintainer : Masaaki Nakamura <mckn@outlook.jp>
|
||||
|
||||
" License : NYSL
|
||||
" Japanese <http://www.kmonos.net/nysl/>
|
||||
" English (Unofficial) <http://www.kmonos.net/nysl/index.en.html>
|
||||
|
||||
if exists("g:loaded_textobj_sandwich")
|
||||
finish
|
||||
endif
|
||||
let g:loaded_textobj_sandwich = 1
|
||||
|
||||
nnoremap <silent><expr> <Plug>(textobj-sandwich-auto-i) textobj#sandwich#auto('n', 'i')
|
||||
onoremap <silent><expr> <Plug>(textobj-sandwich-auto-i) textobj#sandwich#auto('o', 'i')
|
||||
xnoremap <silent><expr> <Plug>(textobj-sandwich-auto-i) textobj#sandwich#auto('x', 'i')
|
||||
nnoremap <silent><expr> <Plug>(textobj-sandwich-auto-a) textobj#sandwich#auto('n', 'a')
|
||||
onoremap <silent><expr> <Plug>(textobj-sandwich-auto-a) textobj#sandwich#auto('o', 'a')
|
||||
xnoremap <silent><expr> <Plug>(textobj-sandwich-auto-a) textobj#sandwich#auto('x', 'a')
|
||||
|
||||
nnoremap <silent><expr> <Plug>(textobj-sandwich-query-i) textobj#sandwich#query('n', 'i')
|
||||
onoremap <silent><expr> <Plug>(textobj-sandwich-query-i) textobj#sandwich#query('o', 'i')
|
||||
xnoremap <silent><expr> <Plug>(textobj-sandwich-query-i) textobj#sandwich#query('x', 'i')
|
||||
nnoremap <silent><expr> <Plug>(textobj-sandwich-query-a) textobj#sandwich#query('n', 'a')
|
||||
onoremap <silent><expr> <Plug>(textobj-sandwich-query-a) textobj#sandwich#query('o', 'a')
|
||||
xnoremap <silent><expr> <Plug>(textobj-sandwich-query-a) textobj#sandwich#query('x', 'a')
|
||||
|
||||
nnoremap <silent><expr> <Plug>(textobj-sandwich-literal-query-i) textobj#sandwich#query('n', 'i', {}, [])
|
||||
onoremap <silent><expr> <Plug>(textobj-sandwich-literal-query-i) textobj#sandwich#query('o', 'i', {}, [])
|
||||
xnoremap <silent><expr> <Plug>(textobj-sandwich-literal-query-i) textobj#sandwich#query('x', 'i', {}, [])
|
||||
nnoremap <silent><expr> <Plug>(textobj-sandwich-literal-query-a) textobj#sandwich#query('n', 'a', {}, [])
|
||||
onoremap <silent><expr> <Plug>(textobj-sandwich-literal-query-a) textobj#sandwich#query('o', 'a', {}, [])
|
||||
xnoremap <silent><expr> <Plug>(textobj-sandwich-literal-query-a) textobj#sandwich#query('x', 'a', {}, [])
|
||||
65
config/neovim/store/lazy-plugins/vim-sandwich/test/.themisrc
Normal file
65
config/neovim/store/lazy-plugins/vim-sandwich/test/.themisrc
Normal file
@ -0,0 +1,65 @@
|
||||
set encoding=utf-8
|
||||
execute 'set runtimepath+=' . expand('<sfile>:p:h:h')
|
||||
runtime! plugin/*.vim
|
||||
runtime! plugin/**/*.vim
|
||||
set noswapfile
|
||||
let g:operator_sandwich_no_visualrepeat = 1
|
||||
|
||||
let g:assert = themis#helper('assert')
|
||||
|
||||
function! TextobjCoord(l1, c1, l2, c2) abort
|
||||
normal! v
|
||||
call cursor(a:l1, a:c1)
|
||||
normal! o
|
||||
call cursor(a:l2, a:c2)
|
||||
endfunction
|
||||
|
||||
function! TextobjFail() abort
|
||||
endfunction
|
||||
|
||||
function! TestIndent() abort
|
||||
" always the indent of the previous nonbland line + shiftwidth()
|
||||
return indent(prevnonblank(v:lnum)) + shiftwidth()
|
||||
endfunction
|
||||
|
||||
function! SandwichExprCancel() abort
|
||||
throw 'OperatorSandwichCancel'
|
||||
endfunction
|
||||
|
||||
function! SandwichExprEmpty() abort
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
function! SandwichExprBuns(is_head) abort
|
||||
if a:is_head
|
||||
return 'head'
|
||||
else
|
||||
return 'tail'
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! SandwichListexprEmpty(which) abort
|
||||
if a:which ==# 'former'
|
||||
return ['', 'bar']
|
||||
elseif a:which ==# 'latter'
|
||||
return ['foo', '']
|
||||
else
|
||||
return ['', '']
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! SandwichListexprBuns(cancel) abort
|
||||
if a:cancel
|
||||
throw 'OperatorSandwichCancel'
|
||||
else
|
||||
return ['foo', 'baz']
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! SandwichSkipIntermediate(is_head, pos, ...) abort
|
||||
if a:is_head
|
||||
return !(a:pos[2] == 1)
|
||||
else
|
||||
return !(a:pos[2] == col([a:pos[1], '$'])-1)
|
||||
endif
|
||||
endfunction
|
||||
@ -0,0 +1,6 @@
|
||||
Use themis.vim (https://github.com/thinca/vim-themis)
|
||||
|
||||
1. git clone https://github.com/thinca/vim-themis.git
|
||||
2. cd path/to/vim-sandwich
|
||||
3. path/to/vim-themis/bin/themis (Linux)
|
||||
path/to/vim-themis/bin/themis.bat (Windows)
|
||||
@ -0,0 +1,11 @@
|
||||
@echo off
|
||||
set VIM=vim
|
||||
if defined THEMIS_VIM set VIM=%THEMIS_VIM%
|
||||
|
||||
%VIM% -u NONE -i NONE -N -n -e -s -S %~dp0\test_dot.vim
|
||||
if %errorlevel% neq 0 goto ERROR
|
||||
echo Succeeded.
|
||||
exit /b 0
|
||||
|
||||
:ERROR
|
||||
exit /b 1
|
||||
@ -0,0 +1,15 @@
|
||||
#! /bin/sh
|
||||
|
||||
SCRIPT_HOME=$0
|
||||
if [ -n "`readlink $SCRIPT_HOME`" ] ; then
|
||||
SCRIPT_HOME="`readlink $SCRIPT_HOME`"
|
||||
fi
|
||||
SCRIPT_HOME="`dirname $SCRIPT_HOME`"
|
||||
|
||||
VIM=vim
|
||||
if [ -n "$THEMIS_VIM" ] ; then
|
||||
VIM="$THEMIS_VIM"
|
||||
fi
|
||||
|
||||
$VIM -u NONE -i NONE -N -n -e -s -S $SCRIPT_HOME/test_dot.vim || exit 1
|
||||
echo "Succeeded."
|
||||
@ -0,0 +1,751 @@
|
||||
if has('win16') || has('win32') || has('win64') || has('win95')
|
||||
set shellslash
|
||||
endif
|
||||
execute 'set runtimepath+=' . expand('<sfile>:p:h:h:h')
|
||||
source <sfile>:p:h:h:h/plugin/operator/sandwich.vim
|
||||
source <sfile>:p:h:h:h/plugin/textobj/sandwich.vim
|
||||
source <sfile>:p:h:h:h/plugin/sandwich.vim
|
||||
nnoremap <Plug>(test-dot) .
|
||||
let g:operator_sandwich_no_visualrepeat = 1
|
||||
|
||||
function! s:assert(a1, a2, kind) abort
|
||||
if type(a:a1) == type(a:a2) && string(a:a1) ==# string(a:a2)
|
||||
return
|
||||
endif
|
||||
|
||||
%delete
|
||||
call append(0, ['Got:', string(a:a1)])
|
||||
call append(0, [printf('Failured at "%s"', a:kind), '', 'Expect:', string(a:a2)])
|
||||
$delete
|
||||
1,$print
|
||||
cquit
|
||||
endfunction
|
||||
|
||||
function! s:quit_by_error() abort
|
||||
%delete
|
||||
call append(0, [printf('Catched the following error at %s.', v:throwpoint), v:exception])
|
||||
$delete
|
||||
1,$print
|
||||
cquit
|
||||
endfunction
|
||||
|
||||
function! Count(...) abort
|
||||
let s:count += 1
|
||||
return s:count
|
||||
endfunction
|
||||
|
||||
function! ListCount(...) abort
|
||||
let s:count += 1
|
||||
return [s:count, s:count]
|
||||
endfunction
|
||||
|
||||
|
||||
|
||||
try
|
||||
|
||||
""" operator-add
|
||||
" normal use
|
||||
call setline('.', 'foo')
|
||||
normal saiw(
|
||||
normal .
|
||||
call s:assert(getline('.'), '((foo))', 'operator-add:normal use #1')
|
||||
call s:assert(getpos('.'), [0, 1, 3, 0], 'operator-add:normal use #2')
|
||||
|
||||
normal saiw[
|
||||
normal .
|
||||
call s:assert(getline('.'), '(([[foo]]))', 'operator-add:normal use #3')
|
||||
call s:assert(getpos('.'), [0, 1, 5, 0], 'operator-add:normal use #4')
|
||||
|
||||
%delete
|
||||
|
||||
" blockwise-visual
|
||||
call append(0, ['foo', 'bar', 'baz'])
|
||||
$delete
|
||||
execute "normal gg\<C-v>2j2lsa("
|
||||
normal .
|
||||
call s:assert(getline(1), '((foo))', 'operator-add:blockwise-visual #1')
|
||||
call s:assert(getline(2), '((bar))', 'operator-add:blockwise-visual #2')
|
||||
call s:assert(getline(3), '((baz))', 'operator-add:blockwise-visual #3')
|
||||
call s:assert(getpos('.'), [0, 1, 3, 0], 'operator-add:blockwise-visual #4')
|
||||
|
||||
normal j.
|
||||
call s:assert(getline(1), '((foo))', 'operator-add:blockwise-visual #5')
|
||||
call s:assert(getline(2), '(((bar)))', 'operator-add:blockwise-visual #6')
|
||||
call s:assert(getline(3), '(((baz)))', 'operator-add:blockwise-visual #7')
|
||||
call s:assert(getpos('.'), [0, 2, 4, 0], 'operator-add:blockwise-visual #8')
|
||||
|
||||
normal j.
|
||||
call s:assert(getline(1), '((foo))', 'operator-add:blockwise-visual #9')
|
||||
call s:assert(getline(2), '(((bar)))', 'operator-add:blockwise-visual #10')
|
||||
call s:assert(getline(3), '((((baz))))', 'operator-add:blockwise-visual #11')
|
||||
call s:assert(getpos('.'), [0, 3, 5, 0], 'operator-add:blockwise-visual #12')
|
||||
|
||||
%delete
|
||||
|
||||
" count
|
||||
call setline('.', 'foo')
|
||||
normal 2saiw((
|
||||
normal .
|
||||
call s:assert(getline('.'), '((((foo))))', 'operator-add:count #1')
|
||||
call s:assert(getpos('.'), [0, 1, 5, 0], 'operator-add:count #2')
|
||||
|
||||
call setline('.', 'foo')
|
||||
normal saiw(
|
||||
call setline('.', 'foo bar')
|
||||
normal 3.
|
||||
call s:assert(getline('.'), '(foo bar)', 'operator-add:count #3')
|
||||
call s:assert(getpos('.'), [0, 1, 2, 0], 'operator-add:count #4')
|
||||
|
||||
%delete
|
||||
|
||||
" expr
|
||||
let g:sandwich#recipes = []
|
||||
let g:operator#sandwich#recipes = [{'buns': ['Count()', 'Count()'], 'expr': 1, 'input': ['c']}]
|
||||
call setline('.', 'foo')
|
||||
let s:count = 0
|
||||
normal saiwc
|
||||
normal .
|
||||
call s:assert(getline('.'), '11foo22', 'operator-add:expr #1')
|
||||
call s:assert(getpos('.'), [0, 1, 2, 0], 'operator-add:expr #2')
|
||||
|
||||
let g:operator#sandwich#recipes = [{'buns': ['Count()', 'Count()'], 'expr': 2, 'input': ['c']}]
|
||||
call setline('.', 'foo')
|
||||
let s:count = 0
|
||||
normal saiwc
|
||||
normal .
|
||||
call s:assert(getline('.'), '31foo24', 'operator-add:expr #3')
|
||||
call s:assert(getpos('.'), [0, 1, 2, 0], 'operator-add:expr #4')
|
||||
|
||||
unlet g:sandwich#recipes
|
||||
unlet g:operator#sandwich#recipes
|
||||
%delete
|
||||
|
||||
" listexpr
|
||||
let g:sandwich#recipes = []
|
||||
let g:operator#sandwich#recipes = [{'buns': 'ListCount()', 'listexpr': 1, 'input': ['c']}]
|
||||
call setline('.', 'foo')
|
||||
let s:count = 0
|
||||
normal saiwc
|
||||
normal .
|
||||
call s:assert(getline('.'), '11foo11', 'operator-add:listexpr #1')
|
||||
call s:assert(getpos('.'), [0, 1, 2, 0], 'operator-add:listexpr #2')
|
||||
|
||||
let g:operator#sandwich#recipes = [{'buns': 'ListCount()', 'listexpr': 2, 'input': ['c']}]
|
||||
call setline('.', 'foo')
|
||||
let s:count = 0
|
||||
normal saiwc
|
||||
normal .
|
||||
call s:assert(getline('.'), '21foo12', 'operator-add:listexpr #3')
|
||||
call s:assert(getpos('.'), [0, 1, 2, 0], 'operator-add:listexpr #4')
|
||||
|
||||
unlet g:sandwich#recipes
|
||||
unlet g:operator#sandwich#recipes
|
||||
%delete
|
||||
|
||||
" cursor option 'keep'
|
||||
call operator#sandwich#set('add', 'all', 'cursor', 'keep')
|
||||
nmap . <Plug>(operator-sandwich-dot)
|
||||
call setline('.', 'foo')
|
||||
normal 0lsaiw(
|
||||
normal .
|
||||
call s:assert(getline('.'), '((foo))', 'operator-add:cursor keep #1')
|
||||
call s:assert(getpos('.'), [0, 1, 4, 0], 'operator-add:cursor keep #2')
|
||||
|
||||
call setline('.', 'foo')
|
||||
normal 0lsaiw(
|
||||
call setline('.', 'foo bar')
|
||||
normal 0l3.
|
||||
call s:assert(getline('.'), '(foo bar)', 'operator-add:cursor keep #3')
|
||||
call s:assert(getpos('.'), [0, 1, 3, 0], 'operator-add:cursor keep #4')
|
||||
|
||||
nmap . <Plug>(operator-sandwich-predot)<Plug>(test-dot)
|
||||
call setline('.', 'foo')
|
||||
normal 0lsaiw(
|
||||
normal .
|
||||
call s:assert(getline('.'), '((foo))', 'operator-add:cursor keep #5')
|
||||
call s:assert(getpos('.'), [0, 1, 4, 0], 'operator-add:cursor keep #6')
|
||||
|
||||
call setline('.', 'foo')
|
||||
normal 0lsaiw(
|
||||
call setline('.', 'foo bar')
|
||||
normal 0l3.
|
||||
call s:assert(getline('.'), '(foo bar)', 'operator-add:cursor keep #7')
|
||||
call s:assert(getpos('.'), [0, 1, 3, 0], 'operator-add:cursor keep #8')
|
||||
|
||||
call operator#sandwich#set_default()
|
||||
nunmap .
|
||||
%delete
|
||||
|
||||
|
||||
|
||||
""" operator-delete
|
||||
nmap sd <Plug>(operator-sandwich-delete)
|
||||
xmap sd <Plug>(operator-sandwich-delete)
|
||||
" normal use
|
||||
call setline('.', '((foo))')
|
||||
normal sda(
|
||||
normal .
|
||||
call s:assert(getline('.'), 'foo', 'operator-delete:normal use #1')
|
||||
call s:assert(getpos('.'), [0, 1, 1, 0], 'operator-delete:normal use #2')
|
||||
|
||||
call setline('.', '[[foo]]')
|
||||
normal sda[
|
||||
normal .
|
||||
call s:assert(getline('.'), 'foo', 'operator-delete:normal use #3')
|
||||
call s:assert(getpos('.'), [0, 1, 1, 0], 'operator-delete:normal use #4')
|
||||
|
||||
%delete
|
||||
|
||||
" blockwise-visual
|
||||
call append(0, ['(((((foo)))))', '(((((bar)))))', '(((((baz)))))'])
|
||||
$delete
|
||||
execute "normal ggffh\<C-v>2j4lsd"
|
||||
normal h.
|
||||
call s:assert(getline(1), '(((foo)))', 'operator-delete:blockwise-visual #1')
|
||||
call s:assert(getline(2), '(((bar)))', 'operator-delete:blockwise-visual #2')
|
||||
call s:assert(getline(3), '(((baz)))', 'operator-delete:blockwise-visual #3')
|
||||
call s:assert(getpos('.'), [0, 1, 4, 0], 'operator-delete:blockwise-visual #4')
|
||||
|
||||
normal jh.
|
||||
call s:assert(getline(1), '(((foo)))', 'operator-delete:blockwise-visual #5')
|
||||
call s:assert(getline(2), '((bar))', 'operator-delete:blockwise-visual #6')
|
||||
call s:assert(getline(3), '((baz))', 'operator-delete:blockwise-visual #7')
|
||||
call s:assert(getpos('.'), [0, 2, 3, 0], 'operator-delete:blockwise-visual #8')
|
||||
|
||||
normal jh.
|
||||
call s:assert(getline(1), '(((foo)))', 'operator-delete:blockwise-visual #9')
|
||||
call s:assert(getline(2), '((bar))', 'operator-delete:blockwise-visual #10')
|
||||
call s:assert(getline(3), '(baz)', 'operator-delete:blockwise-visual #11')
|
||||
call s:assert(getpos('.'), [0, 3, 2, 0], 'operator-delete:blockwise-visual #12')
|
||||
|
||||
%delete
|
||||
|
||||
" count
|
||||
call setline('.', '((((foo))))')
|
||||
normal 02sda(
|
||||
normal .
|
||||
call s:assert(getline('.'), 'foo', 'operator-delete:count #1')
|
||||
call s:assert(getpos('.'), [0, 1, 1, 0], 'operator-delete:count #2')
|
||||
|
||||
call setline('.', '[([[foo]])]')
|
||||
normal ffsda[
|
||||
normal 2.
|
||||
call s:assert(getline('.'), '([foo])', 'operator-delete:count #3')
|
||||
call s:assert(getpos('.'), [0, 1, 1, 0], 'operator-delete:count #4')
|
||||
|
||||
%delete
|
||||
|
||||
" external textobjct
|
||||
let g:sandwich#recipes = []
|
||||
let g:operator#sandwich#recipes = [{'external': ['it', 'at'], 'noremap': 1}]
|
||||
call append(0, ['<title>fooo</title>', '<body>bar</body>'])
|
||||
normal ggsdat
|
||||
normal j.
|
||||
call s:assert(getline(1), 'fooo', 'operator-delete:external textobject #1')
|
||||
call s:assert(getline(2), 'bar', 'operator-delete:external textobject #2')
|
||||
call s:assert(getpos('.'), [0, 2, 1, 0], 'operator-delete:external textobject #3')
|
||||
|
||||
unlet g:sandwich#recipes
|
||||
unlet g:operator#sandwich#recipes
|
||||
%delete
|
||||
|
||||
" cursor option 'keep'
|
||||
call operator#sandwich#set('delete', 'all', 'cursor', 'keep')
|
||||
nmap . <Plug>(operator-sandwich-dot)
|
||||
call setline('.', '((foo))')
|
||||
normal 03lsda(
|
||||
normal .
|
||||
call s:assert(getline('.'), 'foo', 'operator-delete:cursor keep #1')
|
||||
call s:assert(getpos('.'), [0, 1, 2, 0], 'operator-delete:cursor keep #2')
|
||||
|
||||
call setline('.', '(foo)')
|
||||
normal 0sda(
|
||||
call setline('.', '((foo) bar)')
|
||||
normal 03l2.
|
||||
call s:assert(getline('.'), '(foo) bar', 'operator-delete:cursor keep #3')
|
||||
call s:assert(getpos('.'), [0, 1, 3, 0], 'operator-delete:cursor keep #4')
|
||||
|
||||
nmap . <Plug>(operator-sandwich-predot)<Plug>(test-dot)
|
||||
call setline('.', '((foo))')
|
||||
normal 03lsda(
|
||||
normal .
|
||||
call s:assert(getline('.'), 'foo', 'operator-delete:cursor keep #5')
|
||||
call s:assert(getpos('.'), [0, 1, 2, 0], 'operator-delete:cursor keep #6')
|
||||
|
||||
call setline('.', '(foo)')
|
||||
normal 0sda(
|
||||
call setline('.', '((foo) bar)')
|
||||
normal 03l2.
|
||||
call s:assert(getline('.'), '(foo) bar', 'operator-delete:cursor keep #7')
|
||||
call s:assert(getpos('.'), [0, 1, 3, 0], 'operator-delete:cursor keep #8')
|
||||
|
||||
call operator#sandwich#set_default()
|
||||
nunmap .
|
||||
%delete
|
||||
|
||||
|
||||
|
||||
""" operator-replace
|
||||
nmap sr <Plug>(operator-sandwich-replace)
|
||||
xmap sr <Plug>(operator-sandwich-replace)
|
||||
" normal use
|
||||
call setline('.', '((foo))')
|
||||
normal 0ffsra([
|
||||
normal .
|
||||
call s:assert(getline('.'), '[[foo]]', 'operator-replace:normal use #1')
|
||||
call s:assert(getpos('.'), [0, 1, 2, 0], 'operator-replace:normal use #2')
|
||||
|
||||
normal 0ffsra[(
|
||||
normal .
|
||||
call s:assert(getline('.'), '((foo))', 'operator-replace:normal use #3')
|
||||
call s:assert(getpos('.'), [0, 1, 2, 0], 'operator-replace:normal use #4')
|
||||
|
||||
%delete
|
||||
|
||||
" blockwise-visual
|
||||
call append(0, ['(foo)', '(bar)', '(baz)'])
|
||||
$delete
|
||||
execute "normal gg\<C-v>2j4lsr["
|
||||
call setline(1, '(foo)')
|
||||
call setline(2, '(bar)')
|
||||
call setline(3, '(baz)')
|
||||
normal h.
|
||||
call s:assert(getline(1), '[foo]', 'operator-replace:blockwise-visual #1')
|
||||
call s:assert(getline(2), '[bar]', 'operator-replace:blockwise-visual #2')
|
||||
call s:assert(getline(3), '[baz]', 'operator-replace:blockwise-visual #3')
|
||||
call s:assert(getpos('.'), [0, 1, 2, 0], 'operator-replace:blockwise-visual #4')
|
||||
|
||||
call setline(1, '(foo)')
|
||||
call setline(2, '(bar)')
|
||||
call setline(3, '(baz)')
|
||||
normal jh.
|
||||
call s:assert(getline(1), '(foo)', 'operator-replace:blockwise-visual #5')
|
||||
call s:assert(getline(2), '[bar]', 'operator-replace:blockwise-visual #6')
|
||||
call s:assert(getline(3), '[baz]', 'operator-replace:blockwise-visual #7')
|
||||
call s:assert(getpos('.'), [0, 2, 2, 0], 'operator-replace:blockwise-visual #8')
|
||||
|
||||
call setline(1, '(foo)')
|
||||
call setline(2, '(bar)')
|
||||
call setline(3, '(baz)')
|
||||
normal jh.
|
||||
call s:assert(getline(1), '(foo)', 'operator-replace:blockwise-visual #9')
|
||||
call s:assert(getline(2), '(bar)', 'operator-replace:blockwise-visual #10')
|
||||
call s:assert(getline(3), '[baz]', 'operator-replace:blockwise-visual #11')
|
||||
call s:assert(getpos('.'), [0, 3, 2, 0], 'operator-replace:blockwise-visual #12')
|
||||
|
||||
%delete
|
||||
|
||||
" count
|
||||
call setline('.', '((((foo))))')
|
||||
normal 0ff2sr2a([[
|
||||
normal .
|
||||
call s:assert(getline('.'), '[[[[foo]]]]', 'operator-replace:count #1')
|
||||
call s:assert(getpos('.'), [0, 1, 3, 0], 'operator-replace:count #2')
|
||||
|
||||
call setline('.', '[([[foo]])]')
|
||||
normal 0ffsra[(
|
||||
normal 2.
|
||||
call s:assert(getline('.'), '(([(foo)]))', 'operator-replace:count #3')
|
||||
call s:assert(getpos('.'), [0, 1, 2, 0], 'operator-replace:count #2')
|
||||
|
||||
%delete
|
||||
|
||||
" expr
|
||||
let g:sandwich#recipes = []
|
||||
let g:operator#sandwich#recipes = [{'buns': ['Count()', 'Count()'], 'expr': 1, 'input': ['c']}, {'buns': ['(', ')']}]
|
||||
call setline('.', '((foo))')
|
||||
let s:count = 0
|
||||
normal ffsra(c
|
||||
normal .
|
||||
call s:assert(getline('.'), '11foo22', 'operator-replace:expr #1')
|
||||
call s:assert(getpos('.'), [0, 1, 2, 0], 'operator-replace:expr #2')
|
||||
|
||||
let g:operator#sandwich#recipes = [{'buns': ['Count()', 'Count()'], 'expr': 2, 'input': ['c']}, {'buns': ['(', ')']}]
|
||||
call setline('.', '((foo))')
|
||||
let s:count = 0
|
||||
normal ffsra(c
|
||||
normal .
|
||||
call s:assert(getline('.'), '31foo24', 'operator-replace:expr #3')
|
||||
call s:assert(getpos('.'), [0, 1, 2, 0], 'operator-replace:expr #4')
|
||||
|
||||
unlet g:sandwich#recipes
|
||||
unlet g:operator#sandwich#recipes
|
||||
%delete
|
||||
|
||||
" listexpr
|
||||
let g:sandwich#recipes = []
|
||||
let g:operator#sandwich#recipes = [{'buns': 'ListCount()', 'listexpr': 1, 'input': ['c']}, {'buns': ['(', ')']}]
|
||||
call setline('.', '((foo))')
|
||||
let s:count = 0
|
||||
normal ffsra(c
|
||||
normal .
|
||||
call s:assert(getline('.'), '11foo11', 'operator-replace:listexpr #1')
|
||||
call s:assert(getpos('.'), [0, 1, 2, 0], 'operator-replace:listexpr #2')
|
||||
|
||||
let g:operator#sandwich#recipes = [{'buns': 'ListCount()', 'listexpr': 2, 'input': ['c']}, {'buns': ['(', ')']}]
|
||||
call setline('.', '((foo))')
|
||||
let s:count = 0
|
||||
normal ffsra(c
|
||||
normal .
|
||||
call s:assert(getline('.'), '21foo12', 'operator-replace:listexpr #3')
|
||||
call s:assert(getpos('.'), [0, 1, 2, 0], 'operator-replace:listexpr #4')
|
||||
|
||||
unlet g:sandwich#recipes
|
||||
unlet g:operator#sandwich#recipes
|
||||
%delete
|
||||
|
||||
" external textobjct
|
||||
let g:sandwich#recipes = []
|
||||
let g:operator#sandwich#recipes = [{'external': ['it', 'at'], 'noremap': 1}, {'buns': ['(', ')']}]
|
||||
call append(0, ['<title>fooo</title>', '<body>bar</body>'])
|
||||
normal ggsrat(
|
||||
normal j.
|
||||
call s:assert(getline(1), '(fooo)', 'operator-replace:external textobject #1')
|
||||
call s:assert(getline(2), '(bar)', 'operator-replace:external textobject #2')
|
||||
call s:assert(getpos('.'), [0, 2, 2, 0], 'operator-replace:external textobject #3')
|
||||
|
||||
unlet g:sandwich#recipes
|
||||
unlet g:operator#sandwich#recipes
|
||||
%delete
|
||||
|
||||
" cursor option 'keep'
|
||||
call operator#sandwich#set('replace', 'all', 'cursor', 'keep')
|
||||
nmap . <Plug>(operator-sandwich-dot)
|
||||
call setline('.', '((foo))')
|
||||
normal 03lsra([
|
||||
normal .
|
||||
call s:assert(getline('.'), '[[foo]]', 'operator-delete:cursor keep #1')
|
||||
call s:assert(getpos('.'), [0, 1, 4, 0], 'operator-delete:cursor keep #2')
|
||||
|
||||
call setline('.', '(foo)')
|
||||
normal 0sra([
|
||||
call setline('.', '((foo) bar)')
|
||||
normal 03l2.
|
||||
call s:assert(getline('.'), '[(foo) bar]', 'operator-delete:cursor keep #3')
|
||||
call s:assert(getpos('.'), [0, 1, 4, 0], 'operator-delete:cursor keep #4')
|
||||
|
||||
nmap . <Plug>(operator-sandwich-predot)<Plug>(test-dot)
|
||||
call setline('.', '((foo))')
|
||||
normal 03lsra([
|
||||
normal .
|
||||
call s:assert(getline('.'), '[[foo]]', 'operator-delete:cursor keep #5')
|
||||
call s:assert(getpos('.'), [0, 1, 4, 0], 'operator-delete:cursor keep #6')
|
||||
|
||||
call setline('.', '(foo)')
|
||||
normal 0sra([
|
||||
call setline('.', '((foo) bar)')
|
||||
normal 03l2.
|
||||
call s:assert(getline('.'), '[(foo) bar]', 'operator-delete:cursor keep #7')
|
||||
call s:assert(getpos('.'), [0, 1, 4, 0], 'operator-delete:cursor keep #8')
|
||||
|
||||
call operator#sandwich#set_default()
|
||||
nunmap .
|
||||
%delete
|
||||
|
||||
|
||||
|
||||
""" textobj-query
|
||||
" normal use
|
||||
call setline('.', '(foo)')
|
||||
normal dis(
|
||||
call setline('.', '(foo)')
|
||||
normal .
|
||||
call s:assert(getline('.'), '()', 'textobj-query:normal use #1')
|
||||
|
||||
call setline('.', '(foo)')
|
||||
normal das(
|
||||
call setline('.', '(foo)')
|
||||
normal .
|
||||
call s:assert(getline('.'), '', 'textobj-query:normal use #2')
|
||||
|
||||
%delete
|
||||
|
||||
" count
|
||||
call setline('.', '((foo))((bar))')
|
||||
normal 0ffdis(
|
||||
normal 0fb2.
|
||||
call s:assert(getline('.'), '(())()', 'textobj-query:count #1')
|
||||
|
||||
call setline('.', '((foo))((bar))')
|
||||
normal 0ffdas(
|
||||
normal 0fb2.
|
||||
call s:assert(getline('.'), '()', 'textobj-query:count #2')
|
||||
|
||||
%delete
|
||||
|
||||
" expr
|
||||
let g:sandwich#recipes = []
|
||||
let g:textobj#sandwich#recipes = [{'buns': ['Count()', 'Count()'], 'expr': 1, 'input': ['c']}]
|
||||
call setline('.', '1foo2')
|
||||
let s:count = 0
|
||||
normal disc
|
||||
call setline('.', '1foo2')
|
||||
normal .
|
||||
call s:assert(getline('.'), '12', 'textobj-query:expr #1')
|
||||
|
||||
call setline('.', '1foo2')
|
||||
let s:count = 0
|
||||
normal dasc
|
||||
call setline('.', '1foo2')
|
||||
normal .
|
||||
call s:assert(getline('.'), '', 'textobj-query:expr #2')
|
||||
|
||||
let g:sandwich#recipes = []
|
||||
let g:textobj#sandwich#recipes = [{'buns': ['Count()', 'Count()'], 'expr': 2, 'input': ['c']}]
|
||||
call setline('.', '1foo2')
|
||||
let s:count = 0
|
||||
normal disc
|
||||
call setline('.', '3foo4')
|
||||
normal .
|
||||
call s:assert(getline('.'), '34', 'textobj-query:expr #3')
|
||||
|
||||
call setline('.', '1foo2')
|
||||
let s:count = 0
|
||||
normal dasc
|
||||
call setline('.', '3foo4')
|
||||
normal .
|
||||
call s:assert(getline('.'), '', 'textobj-query:expr #4')
|
||||
|
||||
unlet g:sandwich#recipes
|
||||
unlet g:textobj#sandwich#recipes
|
||||
%delete
|
||||
|
||||
" listexpr
|
||||
let g:sandwich#recipes = []
|
||||
let g:textobj#sandwich#recipes = [{'buns': 'ListCount()', 'listexpr': 1, 'input': ['c']}]
|
||||
call setline('.', '1foo1')
|
||||
let s:count = 0
|
||||
normal disc
|
||||
call setline('.', '1foo1')
|
||||
normal .
|
||||
call s:assert(getline('.'), '11', 'textobj-query:listexpr #1')
|
||||
|
||||
call setline('.', '1foo1')
|
||||
let s:count = 0
|
||||
normal dasc
|
||||
call setline('.', '1foo1')
|
||||
normal .
|
||||
call s:assert(getline('.'), '', 'textobj-query:listexpr #2')
|
||||
|
||||
let g:sandwich#recipes = []
|
||||
let g:textobj#sandwich#recipes = [{'buns': 'ListCount()', 'listexpr': 2, 'input': ['c']}]
|
||||
call setline('.', '1foo1')
|
||||
let s:count = 0
|
||||
normal disc
|
||||
call setline('.', '2foo2')
|
||||
normal .
|
||||
call s:assert(getline('.'), '22', 'textobj-query:listexpr #3')
|
||||
|
||||
call setline('.', '1foo1')
|
||||
let s:count = 0
|
||||
normal dasc
|
||||
call setline('.', '2foo2')
|
||||
normal .
|
||||
call s:assert(getline('.'), '', 'textobj-query:listexpr #4')
|
||||
|
||||
unlet g:sandwich#recipes
|
||||
unlet g:textobj#sandwich#recipes
|
||||
%delete
|
||||
|
||||
" external textobjct
|
||||
let g:sandwich#recipes = []
|
||||
let g:textobj#sandwich#recipes = [{'external': ['it', 'at'], 'noremap': 1, 'input': ['t']}]
|
||||
call append(0, ['<title>fooo</title>', '<body>bar</body>'])
|
||||
$delete
|
||||
normal ggdist
|
||||
normal j.
|
||||
call s:assert(getline(1), '<title></title>', 'textobj-query:external textobject #1')
|
||||
call s:assert(getline(2), '<body></body>', 'textobj-query:external textobject #2')
|
||||
|
||||
%delete
|
||||
|
||||
call append(0, ['<title>fooo</title>', '<body>bar</body>'])
|
||||
$delete
|
||||
normal ggdast
|
||||
normal j.
|
||||
call s:assert(getline(1), '', 'textobj-query:external textobject #3')
|
||||
call s:assert(getline(2), '', 'textobj-query:external textobject #4')
|
||||
|
||||
unlet g:sandwich#recipes
|
||||
unlet g:textobj#sandwich#recipes
|
||||
%delete
|
||||
|
||||
" synchro option
|
||||
let g:sandwich#recipes = []
|
||||
let g:textobj#sandwich#recipes = [{'buns': ['foo', 'baz'], 'synchro': 1, 'input': ['f']}]
|
||||
call append(0, ['foo bar baz', ' foo baaaaar baz'])
|
||||
$delete
|
||||
normal ggdisf
|
||||
normal jl.
|
||||
call s:assert(getline(1), 'foobaz', 'textobj-query: synchro option #1')
|
||||
call s:assert(getline(2), ' foobaz', 'textobj-query: synchro option #2')
|
||||
|
||||
%delete
|
||||
|
||||
call append(0, ['foo bar baz', ' foo baaaaar baz'])
|
||||
$delete
|
||||
normal ggdasf
|
||||
normal jl.
|
||||
call s:assert(getline(1), '', 'textobj-query: synchro option #3')
|
||||
call s:assert(getline(2), ' ', 'textobj-query: synchro option #4')
|
||||
|
||||
unlet g:sandwich#recipes
|
||||
unlet g:textobj#sandwich#recipes
|
||||
%delete
|
||||
|
||||
|
||||
|
||||
""" textobj-auto
|
||||
" normal use
|
||||
call setline('.', '(foo)')
|
||||
normal dib
|
||||
call setline('.', '(foo)')
|
||||
normal .
|
||||
call s:assert(getline('.'), '()', 'textobj-auto:normal use #1')
|
||||
|
||||
call setline('.', '(foo)')
|
||||
normal dab
|
||||
call setline('.', '(foo)')
|
||||
normal .
|
||||
call s:assert(getline('.'), '', 'textobj-auto:normal use #2')
|
||||
|
||||
%delete
|
||||
|
||||
" count
|
||||
call setline('.', '((foo))((bar))')
|
||||
normal 0ffdib
|
||||
normal 0fb2.
|
||||
call s:assert(getline('.'), '(())()', 'textobj-auto:count #1')
|
||||
|
||||
call setline('.', '((foo))((bar))')
|
||||
normal 0ffdab
|
||||
normal 0fb2.
|
||||
call s:assert(getline('.'), '()', 'textobj-auto:count #2')
|
||||
|
||||
%delete
|
||||
|
||||
" expr
|
||||
let g:sandwich#recipes = []
|
||||
let g:textobj#sandwich#recipes = [{'buns': ['Count()', 'Count()'], 'expr': 1, 'input': ['c']}]
|
||||
call setline('.', '1foo2')
|
||||
let s:count = 0
|
||||
normal dib
|
||||
call setline('.', '1foo2')
|
||||
normal .
|
||||
call s:assert(getline('.'), '12', 'textobj-auto:expr #1')
|
||||
|
||||
call setline('.', '1foo2')
|
||||
let s:count = 0
|
||||
normal dab
|
||||
call setline('.', '1foo2')
|
||||
normal .
|
||||
call s:assert(getline('.'), '', 'textobj-auto:expr #2')
|
||||
|
||||
let g:sandwich#recipes = []
|
||||
let g:textobj#sandwich#recipes = [{'buns': ['Count()', 'Count()'], 'expr': 2, 'input': ['c']}]
|
||||
call setline('.', '1foo2')
|
||||
let s:count = 0
|
||||
normal dib
|
||||
call setline('.', '3foo4')
|
||||
normal .
|
||||
call s:assert(getline('.'), '34', 'textobj-auto:expr #3')
|
||||
|
||||
call setline('.', '1foo2')
|
||||
let s:count = 0
|
||||
normal dab
|
||||
call setline('.', '3foo4')
|
||||
normal .
|
||||
call s:assert(getline('.'), '', 'textobj-auto:expr #4')
|
||||
|
||||
unlet g:sandwich#recipes
|
||||
unlet g:textobj#sandwich#recipes
|
||||
%delete
|
||||
|
||||
" listexpr
|
||||
let g:sandwich#recipes = []
|
||||
let g:textobj#sandwich#recipes = [{'buns': 'ListCount()', 'listexpr': 1, 'input': ['c']}]
|
||||
call setline('.', '1foo1')
|
||||
let s:count = 0
|
||||
normal dib
|
||||
call setline('.', '1foo1')
|
||||
normal .
|
||||
call s:assert(getline('.'), '11', 'textobj-query:listexpr #1')
|
||||
|
||||
call setline('.', '1foo1')
|
||||
let s:count = 0
|
||||
normal dab
|
||||
call setline('.', '1foo1')
|
||||
normal .
|
||||
call s:assert(getline('.'), '', 'textobj-query:listexpr #2')
|
||||
|
||||
let g:sandwich#recipes = []
|
||||
let g:textobj#sandwich#recipes = [{'buns': 'ListCount()', 'listexpr': 2, 'input': ['c']}]
|
||||
call setline('.', '1foo1')
|
||||
let s:count = 0
|
||||
normal dib
|
||||
call setline('.', '2foo2')
|
||||
normal .
|
||||
call s:assert(getline('.'), '22', 'textobj-query:listexpr #3')
|
||||
|
||||
call setline('.', '1foo1')
|
||||
let s:count = 0
|
||||
normal dab
|
||||
call setline('.', '2foo2')
|
||||
normal .
|
||||
call s:assert(getline('.'), '', 'textobj-query:listexpr #4')
|
||||
|
||||
unlet g:sandwich#recipes
|
||||
unlet g:textobj#sandwich#recipes
|
||||
%delete
|
||||
|
||||
" external textobjct
|
||||
let g:sandwich#recipes = []
|
||||
let g:textobj#sandwich#recipes = [{'external': ['it', 'at'], 'noremap': 1, 'input': ['t']}]
|
||||
call append(0, ['<title>fooo</title>', '<body>bar</body>'])
|
||||
$delete
|
||||
normal ggdib
|
||||
normal j.
|
||||
call s:assert(getline(1), '<title></title>', 'textobj-auto:external textobject #1')
|
||||
call s:assert(getline(2), '<body></body>', 'textobj-auto:external textobject #2')
|
||||
|
||||
%delete
|
||||
|
||||
call append(0, ['<title>fooo</title>', '<body>bar</body>'])
|
||||
$delete
|
||||
normal ggdab
|
||||
normal j.
|
||||
call s:assert(getline(1), '', 'textobj-auto:external textobject #3')
|
||||
call s:assert(getline(2), '', 'textobj-auto:external textobject #4')
|
||||
|
||||
unlet g:sandwich#recipes
|
||||
unlet g:textobj#sandwich#recipes
|
||||
%delete
|
||||
|
||||
" synchro option
|
||||
let g:sandwich#recipes = []
|
||||
let g:textobj#sandwich#recipes = [{'buns': ['foo', 'baz'], 'synchro': 1, 'input': ['f']}]
|
||||
call append(0, ['foo bar baz', ' foo baaaaar baz'])
|
||||
$delete
|
||||
normal ggdib
|
||||
normal jl.
|
||||
call s:assert(getline(1), 'foobaz', 'textobj-auto: synchro option #1')
|
||||
call s:assert(getline(2), ' foobaz', 'textobj-auto: synchro option #2')
|
||||
|
||||
%delete
|
||||
|
||||
call append(0, ['foo bar baz', ' foo baaaaar baz'])
|
||||
$delete
|
||||
normal ggdab
|
||||
normal jl.
|
||||
call s:assert(getline(1), '', 'textobj-auto: synchro option #3')
|
||||
call s:assert(getline(2), ' ', 'textobj-auto: synchro option #4')
|
||||
|
||||
%delete
|
||||
|
||||
catch
|
||||
call s:quit_by_error()
|
||||
endtry
|
||||
|
||||
|
||||
|
||||
qall!
|
||||
11075
config/neovim/store/lazy-plugins/vim-sandwich/test/operator-add.vim
Normal file
11075
config/neovim/store/lazy-plugins/vim-sandwich/test/operator-add.vim
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
14236
config/neovim/store/lazy-plugins/vim-sandwich/test/operator-replace.vim
Normal file
14236
config/neovim/store/lazy-plugins/vim-sandwich/test/operator-replace.vim
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,80 @@
|
||||
scriptencoding utf-8
|
||||
|
||||
let s:suite = themis#suite('keymappings')
|
||||
|
||||
function! s:suite.before() abort "{{{
|
||||
runtime macros/sandwich/keymap/surround.vim
|
||||
endfunction "}}}
|
||||
function! s:suite.before_each() abort "{{{
|
||||
%delete
|
||||
set filetype=
|
||||
call operator#sandwich#set_default()
|
||||
unlet! g:sandwich#recipes
|
||||
unlet! g:operator#sandwich#recipes
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:suite.after() abort "{{{
|
||||
call s:suite.before_each()
|
||||
mapclear
|
||||
unlet g:sandwich_no_default_key_mappings
|
||||
unlet g:operator_sandwich_no_default_key_mappings
|
||||
unlet g:textobj_sandwich_no_default_key_mappings
|
||||
runtime plugin/sandwich.vim
|
||||
runtime plugin/operator/sandwich.vim
|
||||
runtime plugin/textobj/sandwich.vim
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
|
||||
function! s:suite.surround() abort "{{{
|
||||
call setline('.', 'foo')
|
||||
normal 0ysiw(
|
||||
call g:assert.equals(getline('.'), '(foo)', 'failed at #1')
|
||||
|
||||
call setline('.', 'foo bar')
|
||||
normal 0yss(
|
||||
call g:assert.equals(getline('.'), '(foo bar)', 'failed at #2')
|
||||
|
||||
call setline('.', 'foo bar')
|
||||
normal 04lyS(
|
||||
call g:assert.equals(getline('.'), 'foo (bar)', 'failed at #3')
|
||||
|
||||
call setline('.', '[(foo)]')
|
||||
normal 02lds(
|
||||
call g:assert.equals(getline('.'), '[foo]', 'failed at #4')
|
||||
|
||||
call setline('.', '[(foo)]')
|
||||
normal 02lds[
|
||||
call g:assert.equals(getline('.'), '(foo)', 'failed at #5')
|
||||
|
||||
call setline('.', '[(foo)]')
|
||||
normal 02ldss
|
||||
call g:assert.equals(getline('.'), '[foo]', 'failed at #6')
|
||||
|
||||
call setline('.', '"''foo''"')
|
||||
normal 02ldss
|
||||
call g:assert.equals(getline('.'), '"foo"', 'failed at #7')
|
||||
|
||||
call setline('.', '[(foo)]')
|
||||
normal 02lcs({
|
||||
call g:assert.equals(getline('.'), '[{foo}]', 'failed at #8')
|
||||
|
||||
call setline('.', '[(foo)]')
|
||||
normal 02lcs[{
|
||||
call g:assert.equals(getline('.'), '{(foo)}', 'failed at #9')
|
||||
|
||||
call setline('.', '[(foo)]')
|
||||
normal 02lcss{
|
||||
call g:assert.equals(getline('.'), '[{foo}]', 'failed at #10')
|
||||
|
||||
call setline('.', '"''foo''"')
|
||||
normal 02lcss`
|
||||
call g:assert.equals(getline('.'), '"`foo`"', 'failed at #11')
|
||||
|
||||
call setline('.', 'foo')
|
||||
normal 0viwS(
|
||||
call g:assert.equals(getline('.'), '(foo)', 'failed at #12')
|
||||
endfunction "}}}
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
@ -0,0 +1,72 @@
|
||||
scriptencoding utf-8
|
||||
|
||||
let s:suite = themis#suite('magicchar-f: ')
|
||||
|
||||
function! s:suite.before() abort "{{{
|
||||
nmap sd <Plug>(sandwich-delete)
|
||||
xmap sd <Plug>(sandwich-delete)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:suite.before_each() abort "{{{
|
||||
%delete
|
||||
set filetype=
|
||||
unlet! g:sandwich#magicchar#f#patterns
|
||||
unlet! b:sandwich_magicchar_f_patterns
|
||||
unlet! g:sandwich#recipes g:operator#sandwich#recipes
|
||||
unlet! b:sandwich_recipes b:operator_sandwich_recipes
|
||||
call operator#sandwich#set_default()
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:suite.after() abort "{{{
|
||||
call s:suite.before_each()
|
||||
nunmap sd
|
||||
xunmap sd
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
function! s:suite.default_pattern() abort "{{{
|
||||
call setline('.', 'foo(bar)')
|
||||
call cursor(1, 1)
|
||||
normal sdf
|
||||
call g:assert.equals(getline('.'), 'bar', 'failed at #1')
|
||||
endfunction "}}}
|
||||
function! s:suite.global_pattern() abort "{{{
|
||||
let g:sandwich#magicchar#f#patterns = [
|
||||
\ {
|
||||
\ 'header' : '\<\%(\h\k*\.\)*\h\k*',
|
||||
\ 'bra' : '(',
|
||||
\ 'ket' : ')',
|
||||
\ 'footer' : '',
|
||||
\ },
|
||||
\ ]
|
||||
call setline('.', 'foo.bar(baz)')
|
||||
call cursor(1, 5)
|
||||
normal sdf
|
||||
call g:assert.equals(getline('.'), 'baz', 'failed at #1')
|
||||
endfunction "}}}
|
||||
function! s:suite.local_pattern() abort "{{{
|
||||
let g:sandwich#magicchar#f#patterns = [
|
||||
\ {
|
||||
\ 'header' : '\<\h\k*\.\h\k*',
|
||||
\ 'bra' : '(',
|
||||
\ 'ket' : ')',
|
||||
\ 'footer' : '',
|
||||
\ },
|
||||
\ ]
|
||||
let b:sandwich_magicchar_f_patterns = [
|
||||
\ {
|
||||
\ 'header' : '\<\%(\h\k*\.\)*\h\k*',
|
||||
\ 'bra' : '(',
|
||||
\ 'ket' : ')',
|
||||
\ 'footer' : '',
|
||||
\ },
|
||||
\ ]
|
||||
call setline('.', 'foo.bar.baz(qux)')
|
||||
call cursor(1, 9)
|
||||
normal sdf
|
||||
call g:assert.equals(getline('.'), 'qux', 'failed at #1')
|
||||
endfunction "}}}
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
@ -0,0 +1,536 @@
|
||||
scriptencoding utf-8
|
||||
|
||||
let s:suite = themis#suite('magicchar-t emmet-like behavior:')
|
||||
|
||||
let s:scope = themis#helper('scope')
|
||||
let s:t = s:scope.funcs('autoload/sandwich/magicchar/t.vim')
|
||||
|
||||
" test seeds "{{{
|
||||
let s:testseeds = {}
|
||||
let s:testseeds.element = [
|
||||
\ {
|
||||
\ 'input': 'element',
|
||||
\ 'token': ['element'],
|
||||
\ 'items': [{'name': 'element', 'value': 'element'}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': '',
|
||||
\ 'token': [],
|
||||
\ 'items': [{'name': 'element', 'value': 'div'}],
|
||||
\ },
|
||||
\ ]
|
||||
let s:testseeds.attributes = [
|
||||
\ {
|
||||
\ 'input': '#id1',
|
||||
\ 'token': ['#', 'id1'],
|
||||
\ 'items': [{'name': 'id', 'value': 'id1'}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': '#id2',
|
||||
\ 'token': ['#', 'id2'],
|
||||
\ 'items': [{'name': 'id', 'value': 'id2'}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': '#',
|
||||
\ 'token': ['#'],
|
||||
\ 'items': [{'name': 'id', 'value': ''}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': '.class1',
|
||||
\ 'token': ['.', 'class1'],
|
||||
\ 'items': [{'name': 'class', 'value': ['class1']}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': '.class2',
|
||||
\ 'token': ['.', 'class2'],
|
||||
\ 'items': [{'name': 'class', 'value': ['class2']}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': '.',
|
||||
\ 'token': ['.'],
|
||||
\ 'items': [{'name': 'class', 'value': ['']}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': '[attr=hello]',
|
||||
\ 'token': ['[', 'attr', '=', 'hello', ']'],
|
||||
\ 'items': [{'name': 'attr', 'value': 'hello'}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': '[attr=world]',
|
||||
\ 'token': ['[', 'attr', '=', 'world', ']'],
|
||||
\ 'items': [{'name': 'attr', 'value': 'world'}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': '[hello=world]',
|
||||
\ 'token': ['[', 'hello', '=', 'world', ']'],
|
||||
\ 'items': [{'name': 'hello', 'value': 'world'}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': '[]',
|
||||
\ 'token': ['[', ']'],
|
||||
\ 'items': [],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': '[attr="including space"]',
|
||||
\ 'token': ['[', 'attr', '=', '"including space"', ']'],
|
||||
\ 'items': [{'name': 'attr', 'value': '"including space"'}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': '[ attr="including space"]',
|
||||
\ 'token': ['[', ' ', 'attr', '=', '"including space"', ']'],
|
||||
\ 'items': [{'name': 'attr', 'value': '"including space"'}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': '[attr ="including space"]',
|
||||
\ 'token': ['[', 'attr', ' ', '=', '"including space"', ']'],
|
||||
\ 'items': [
|
||||
\ {'name': 'attr', 'value': ''},
|
||||
\ {'name': '', 'value': '"including space"'},
|
||||
\ ],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': '[attr= "including space"]',
|
||||
\ 'token': ['[', 'attr', '=', ' ', '"including space"', ']'],
|
||||
\ 'items': [
|
||||
\ {'name': 'attr', 'value': ''},
|
||||
\ {'name': '"including space"', 'value': ''},
|
||||
\ ],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': '[attr="including space" ]',
|
||||
\ 'token': ['[', 'attr', '=', '"including space"', ' ', ']'],
|
||||
\ 'items': [{'name': 'attr', 'value': '"including space"'}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[attr='including space']",
|
||||
\ 'token': ['[', 'attr', '=', "'including space'", ']'],
|
||||
\ 'items': [{'name': 'attr', 'value': "'including space'"}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[ attr='including space']",
|
||||
\ 'token': ['[', ' ', 'attr', '=', "'including space'", ']'],
|
||||
\ 'items': [{'name': 'attr', 'value': "'including space'"}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[attr ='including space']",
|
||||
\ 'token': ['[', 'attr', ' ', '=', "'including space'", ']'],
|
||||
\ 'items': [
|
||||
\ {'name': 'attr', 'value': ""},
|
||||
\ {'name': '', 'value': "'including space'"},
|
||||
\ ],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[attr= 'including space']",
|
||||
\ 'token': ['[', 'attr', '=', ' ', "'including space'", ']'],
|
||||
\ 'items': [
|
||||
\ {'name': 'attr', 'value': ""},
|
||||
\ {'name': "'including space'", 'value': ''},
|
||||
\ ],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[attr='including space' ]",
|
||||
\ 'token': ['[', 'attr', '=', "'including space'", ' ', ']'],
|
||||
\ 'items': [{'name': 'attr', 'value': "'including space'"}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[attr=withoutspace]",
|
||||
\ 'token': ['[', 'attr', '=', 'withoutspace', ']'],
|
||||
\ 'items': [{'name': 'attr', 'value': 'withoutspace'}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[ attr=withoutspace]",
|
||||
\ 'token': ['[', ' ', 'attr', '=', 'withoutspace', ']'],
|
||||
\ 'items': [{'name': 'attr', 'value': 'withoutspace'}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[attr =withoutspace]",
|
||||
\ 'token': ['[', 'attr', ' ', '=', 'withoutspace', ']'],
|
||||
\ 'items': [
|
||||
\ {'name': 'attr', 'value': ''},
|
||||
\ {'name': '', 'value': 'withoutspace'},
|
||||
\ ],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[attr= withoutspace]",
|
||||
\ 'token': ['[', 'attr', '=', ' ', 'withoutspace', ']'],
|
||||
\ 'items': [
|
||||
\ {'name': 'attr', 'value': ''},
|
||||
\ {'name': 'withoutspace', 'value': ''},
|
||||
\ ],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[attr=withoutspace ]",
|
||||
\ 'token': ['[', 'attr', '=', 'withoutspace', ' ', ']'],
|
||||
\ 'items': [{'name': 'attr', 'value': 'withoutspace'}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[attr=]",
|
||||
\ 'token': ['[', 'attr', '=', ']'],
|
||||
\ 'items': [{'name': 'attr', 'value': ''}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[ attr=]",
|
||||
\ 'token': ['[', ' ', 'attr', '=', ']'],
|
||||
\ 'items': [{'name': 'attr', 'value': ''}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[attr =]",
|
||||
\ 'token': ['[', 'attr', ' ', '=', ']'],
|
||||
\ 'items': [
|
||||
\ {'name': 'attr', 'value': ''},
|
||||
\ {'name': '', 'value': ''},
|
||||
\ ],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[attr= ]",
|
||||
\ 'token': ['[', 'attr', '=', ' ', ']'],
|
||||
\ 'items': [{'name': 'attr', 'value': ''}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[=value]",
|
||||
\ 'token': ['[', '=', 'value', ']'],
|
||||
\ 'items': [{'name': '', 'value': 'value'}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[ =value]",
|
||||
\ 'token': ['[', ' ', '=', 'value', ']'],
|
||||
\ 'items': [{'name': '', 'value': 'value'}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[= value]",
|
||||
\ 'token': ['[', '=', ' ', 'value', ']'],
|
||||
\ 'items': [
|
||||
\ {'name': '', 'value': ''},
|
||||
\ {'name': 'value', 'value': ''},
|
||||
\ ],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[=value ]",
|
||||
\ 'token': ['[', '=', 'value', ' ', ']'],
|
||||
\ 'items': [{'name': '', 'value': 'value'}],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[==]",
|
||||
\ 'token': ['[', '=', '=', ']'],
|
||||
\ 'items': [
|
||||
\ {'name': '', 'value': ''},
|
||||
\ {'name': '', 'value': ''},
|
||||
\ ],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[ ==]",
|
||||
\ 'token': ['[', ' ', '=', '=', ']'],
|
||||
\ 'items': [
|
||||
\ {'name': '', 'value': ''},
|
||||
\ {'name': '', 'value': ''},
|
||||
\ ],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[= =]",
|
||||
\ 'token': ['[', '=', ' ', '=', ']'],
|
||||
\ 'items': [
|
||||
\ {'name': '', 'value': ''},
|
||||
\ {'name': '', 'value': ''},
|
||||
\ ],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[== ]",
|
||||
\ 'token': ['[', '=', '=', ' ', ']'],
|
||||
\ 'items': [
|
||||
\ {'name': '', 'value': ''},
|
||||
\ {'name': '', 'value': ''},
|
||||
\ ],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[attr==]",
|
||||
\ 'token': ['[', 'attr', '=', '=', ']'],
|
||||
\ 'items': [
|
||||
\ {'name': 'attr', 'value': ''},
|
||||
\ {'name': '', 'value': ''},
|
||||
\ ],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[=value=]",
|
||||
\ 'token': ['[', '=', 'value', '=', ']'],
|
||||
\ 'items': [
|
||||
\ {'name': '', 'value': 'value'},
|
||||
\ {'name': '', 'value': ''},
|
||||
\ ],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[==value]",
|
||||
\ 'token': ['[', '=', '=', 'value', ']'],
|
||||
\ 'items': [
|
||||
\ {'name': '', 'value': ''},
|
||||
\ {'name': '', 'value': 'value'},
|
||||
\ ],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "['word1 word2'==]",
|
||||
\ 'token': ['[', "'word1 word2'", '=', '=', ']'],
|
||||
\ 'items': [
|
||||
\ {'name': "'word1 word2'", 'value': ''},
|
||||
\ {'name': '', 'value': ''},
|
||||
\ ],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[='word1 word2'=]",
|
||||
\ 'token': ['[', '=', "'word1 word2'", '=', ']'],
|
||||
\ 'items': [
|
||||
\ {'name': '', 'value': "'word1 word2'"},
|
||||
\ {'name': '', 'value': ''},
|
||||
\ ],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[=='word1 word2']",
|
||||
\ 'token': ['[', '=', '=', "'word1 word2'", ']'],
|
||||
\ 'items': [
|
||||
\ {'name': '', 'value': ''},
|
||||
\ {'name': '', 'value': "'word1 word2'"},
|
||||
\ ],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[attr1=value1 attr2=value2]",
|
||||
\ 'token': ['[', 'attr1', '=', 'value1', ' ', 'attr2', '=', 'value2', ']'],
|
||||
\ 'items': [
|
||||
\ {'name': 'attr1', 'value': 'value1'},
|
||||
\ {'name': 'attr2', 'value': 'value2'},
|
||||
\ ],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[attr1=value1 attr2]",
|
||||
\ 'token': ['[', 'attr1', '=', 'value1', ' ', 'attr2', ']'],
|
||||
\ 'items': [
|
||||
\ {'name': 'attr1', 'value': 'value1'},
|
||||
\ {'name': 'attr2', 'value': ''},
|
||||
\ ],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[attr1 attr2=value2]",
|
||||
\ 'token': ['[', 'attr1', ' ', 'attr2', '=', 'value2', ']'],
|
||||
\ 'items': [
|
||||
\ {'name': 'attr1', 'value': ''},
|
||||
\ {'name': 'attr2', 'value': 'value2'},
|
||||
\ ],
|
||||
\ },
|
||||
\
|
||||
\ {
|
||||
\ 'input': "[attr1 attr2]",
|
||||
\ 'token': ['[', 'attr1', ' ', 'attr2', ']'],
|
||||
\ 'items': [
|
||||
\ {'name': 'attr1', 'value': ''},
|
||||
\ {'name': 'attr2', 'value': ''},
|
||||
\ ],
|
||||
\ },
|
||||
\ ]
|
||||
function! s:testseeds.generate_parsed(itemlist) dict abort "{{{
|
||||
let itemlist = deepcopy(a:itemlist)
|
||||
call s:overwrite(itemlist, 'id')
|
||||
call s:append(itemlist, 'class')
|
||||
let custom_attr_list = map(filter(deepcopy(itemlist), 'has_key(v:val, "name") && v:val.name !~# ''\%(element\|id\|class\)'''), 'v:val.name')
|
||||
call s:uniq(filter(custom_attr_list, 'v:val !=# ""'))
|
||||
for attr in custom_attr_list
|
||||
call s:overwrite(itemlist, attr)
|
||||
endfor
|
||||
return itemlist
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:overwrite(itemlist, name) abort "{{{
|
||||
let i = 0
|
||||
let n = len(a:itemlist)
|
||||
let i_target = -1
|
||||
while i < n
|
||||
let item = a:itemlist[i]
|
||||
if item.name ==# a:name
|
||||
let i_target = i
|
||||
break
|
||||
endif
|
||||
let i += 1
|
||||
endwhile
|
||||
if i_target > 0
|
||||
let i = n - 1
|
||||
let value = ''
|
||||
let value_is_fixed = 0
|
||||
while i > i_target
|
||||
let item = a:itemlist[i]
|
||||
if item.name ==# a:name
|
||||
if !value_is_fixed
|
||||
let value = item.value
|
||||
let value_is_fixed = 1
|
||||
endif
|
||||
call remove(a:itemlist, i)
|
||||
endif
|
||||
let i -= 1
|
||||
endwhile
|
||||
if value_is_fixed
|
||||
let a:itemlist[i_target]['value'] = value
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:append(itemlist, name) abort "{{{
|
||||
let i = 0
|
||||
let n = len(a:itemlist)
|
||||
let i_target = -1
|
||||
while i < n
|
||||
let item = a:itemlist[i]
|
||||
if item.name ==# a:name
|
||||
let i_target = i
|
||||
break
|
||||
endif
|
||||
let i += 1
|
||||
endwhile
|
||||
if i_target > 0
|
||||
let i = n - 1
|
||||
let value = []
|
||||
while i > i_target
|
||||
let item = a:itemlist[i]
|
||||
if item.name ==# a:name
|
||||
let value += item.value
|
||||
call remove(a:itemlist, i)
|
||||
endif
|
||||
let i -= 1
|
||||
endwhile
|
||||
if value !=# []
|
||||
let a:itemlist[i_target]['value'] += reverse(value)
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:uniq(list) abort "{{{
|
||||
let i = len(a:list) - 1
|
||||
while i > 0
|
||||
let item = a:list[i]
|
||||
if count(a:list, item) > 1
|
||||
call remove(a:list, i)
|
||||
endif
|
||||
let i -= 1
|
||||
endwhile
|
||||
return a:list
|
||||
endfunction
|
||||
"}}}
|
||||
"}}}
|
||||
|
||||
function! s:suite.tokenize() dict abort "{{{
|
||||
" happy paths
|
||||
|
||||
" 1 seed
|
||||
for element in deepcopy(s:testseeds.element)
|
||||
for attribute1 in deepcopy(s:testseeds.attributes)
|
||||
let input = join([element.input, attribute1.input], '')
|
||||
let expect = element.token + attribute1.token
|
||||
call g:assert.equals(s:t.tokenize(input), expect, 'input: ' . input)
|
||||
endfor
|
||||
endfor
|
||||
|
||||
" 2 seeds
|
||||
for element in deepcopy(s:testseeds.element)
|
||||
for attribute1 in deepcopy(s:testseeds.attributes)
|
||||
for attribute2 in deepcopy(s:testseeds.attributes)
|
||||
let input = join([element.input, attribute1.input, attribute2.input], '')
|
||||
let expect = element.token + attribute1.token + attribute2.token
|
||||
call g:assert.equals(s:t.tokenize(input), expect, 'input: ' . input)
|
||||
endfor
|
||||
endfor
|
||||
endfor
|
||||
|
||||
" " 3 seeds
|
||||
" for element in deepcopy(s:testseeds.element)
|
||||
" for attribute1 in deepcopy(s:testseeds.attributes)
|
||||
" for attribute2 in deepcopy(s:testseeds.attributes)
|
||||
" for attribute3 in deepcopy(s:testseeds.attributes)
|
||||
" let input = join([element.input, attribute1.input, attribute2.input, attribute3.input], '')
|
||||
" let expect = element.token + attribute1.token + attribute2.token + attribute3.token
|
||||
" call g:assert.equals(s:t.tokenize(input), expect, 'input: ' . input)
|
||||
" endfor
|
||||
" endfor
|
||||
" endfor
|
||||
" endfor
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:suite.parse() dict abort "{{{
|
||||
" happy paths
|
||||
|
||||
" 1 seed
|
||||
for element in deepcopy(s:testseeds.element)
|
||||
for attribute1 in deepcopy(s:testseeds.attributes)
|
||||
let input = element.token + attribute1.token
|
||||
let expect = s:testseeds.generate_parsed(element.items + attribute1.items)
|
||||
call g:assert.equals(s:t.parse(input), expect, 'input: ' . string(input))
|
||||
endfor
|
||||
endfor
|
||||
|
||||
" 2 seeds
|
||||
for element in deepcopy(s:testseeds.element)
|
||||
for attribute1 in deepcopy(s:testseeds.attributes)
|
||||
for attribute2 in deepcopy(s:testseeds.attributes)
|
||||
let input = element.token + attribute1.token + attribute2.token
|
||||
let expect = s:testseeds.generate_parsed(element.items + attribute1.items + attribute2.items)
|
||||
call g:assert.equals(s:t.parse(input), expect, 'input: ' . string(input))
|
||||
endfor
|
||||
endfor
|
||||
endfor
|
||||
|
||||
" " 3 seeds
|
||||
" for element in deepcopy(s:testseeds.element)
|
||||
" for attribute1 in deepcopy(s:testseeds.attributes)
|
||||
" for attribute2 in deepcopy(s:testseeds.attributes)
|
||||
" for attribute3 in deepcopy(s:testseeds.attributes)
|
||||
" let input = element.token + attribute1.token + attribute2.token + attribute3.token
|
||||
" let expect = s:testseeds.generate_parsed(element.items + attribute1.items + attribute2.items + attribute3.items)
|
||||
" call g:assert.equals(s:t.parse(input), expect, 'input: ' . string(input))
|
||||
" endfor
|
||||
" endfor
|
||||
" endfor
|
||||
" endfor
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
@ -0,0 +1,91 @@
|
||||
scriptencoding utf-8
|
||||
|
||||
let s:suite = themis#suite('magicchar-t:')
|
||||
|
||||
let s:scope = themis#helper('scope')
|
||||
let s:t = s:scope.funcs('autoload/sandwich/magicchar/t.vim')
|
||||
|
||||
|
||||
function! s:suite.before() abort "{{{
|
||||
nmap sa <Plug>(sandwich-add)
|
||||
xmap sa <Plug>(sandwich-add)
|
||||
omap sa <Plug>(sandwich-add)
|
||||
nmap sd <Plug>(sandwich-delete)
|
||||
xmap sd <Plug>(sandwich-delete)
|
||||
nmap sr <Plug>(sandwich-replace)
|
||||
xmap sr <Plug>(sandwich-replace)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:suite.after() abort "{{{
|
||||
nunmap sa
|
||||
xunmap sa
|
||||
ounmap sa
|
||||
nunmap sd
|
||||
xunmap sd
|
||||
nunmap sr
|
||||
xunmap sr
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
function! s:suite.add() abort "{{{
|
||||
" #1
|
||||
call setline(1, 'foo')
|
||||
execute "normal 1Gsaiwtp\<CR>"
|
||||
call g:assert.equals(getline(1), '<p>foo</p>', 'failed at #1')
|
||||
|
||||
" #2
|
||||
call setline(1, 'foo')
|
||||
execute "normal 1GsaiwTp\<CR>"
|
||||
call g:assert.equals(getline(1), '<p>foo</p>', 'failed at #2')
|
||||
endfunction "}}}
|
||||
function! s:suite.delete() abort "{{{
|
||||
" #1
|
||||
call setline(1, '<p>foo</p>')
|
||||
normal 1Gsdt
|
||||
call g:assert.equals(getline(1), 'foo', 'failed at #1')
|
||||
|
||||
" #2
|
||||
call setline(1, '<p>foo</p>')
|
||||
normal 1GsdT
|
||||
call g:assert.equals(getline(1), 'foo', 'failed at #2')
|
||||
endfunction "}}}
|
||||
function! s:suite.replace() abort "{{{
|
||||
" #1
|
||||
call setline(1, '<p>foo</p>')
|
||||
execute "normal 1Gffsrttdiv\<CR>"
|
||||
call g:assert.equals(getline(1), '<div>foo</div>', 'failed at #1')
|
||||
|
||||
" #2
|
||||
call setline(1, '<p>foo</p>')
|
||||
execute "normal 1GffsrTTdiv\<CR>"
|
||||
call g:assert.equals(getline(1), '<div>foo</div>', 'failed at #2')
|
||||
|
||||
" #3
|
||||
call setline(1, '<div title="foo">foo</div>')
|
||||
execute "normal 1Gffsrttp\<CR>"
|
||||
call g:assert.equals(getline(1), '<p title="foo">foo</p>', 'failed at #3')
|
||||
|
||||
" #4
|
||||
call setline(1, '<div title="foo">foo</div>')
|
||||
execute "normal 1GffsrTTp\<CR>"
|
||||
call g:assert.equals(getline(1), '<p>foo</p>', 'failed at #4')
|
||||
endfunction "}}}
|
||||
function! s:suite.add_selection_exclusive() abort "{{{
|
||||
set selection=exclusive
|
||||
call s:suite.add()
|
||||
set selection=inclusive
|
||||
endfunction "}}}
|
||||
function! s:suite.delete_selection_exclusive() abort "{{{
|
||||
set selection=exclusive
|
||||
call s:suite.delete()
|
||||
set selection=inclusive
|
||||
endfunction "}}}
|
||||
function! s:suite.replace_selection_exclusive() abort "{{{
|
||||
set selection=exclusive
|
||||
call s:suite.replace()
|
||||
set selection=inclusive
|
||||
endfunction "}}}
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
5800
config/neovim/store/lazy-plugins/vim-sandwich/test/textobj-auto.vim
Normal file
5800
config/neovim/store/lazy-plugins/vim-sandwich/test/textobj-auto.vim
Normal file
File diff suppressed because it is too large
Load Diff
5628
config/neovim/store/lazy-plugins/vim-sandwich/test/textobj-query.vim
Normal file
5628
config/neovim/store/lazy-plugins/vim-sandwich/test/textobj-query.vim
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user