├── .github └── workflows │ └── linux_vim.yml ├── README.md ├── autoload └── lexiv.vim ├── plugin └── lexiv.vim └── test ├── .themisrc └── lexiv.vimspec /.github/workflows/linux_vim.yml: -------------------------------------------------------------------------------- 1 | name: linux_vim 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: [ubuntu-latest] 17 | name: [vim-v82-x64, vim-v81-x64] 18 | include: 19 | - name: vim-v82-x64 20 | os: ubuntu-latest 21 | vim_version: 8.2.0037 22 | glibc_version: 2.15 23 | - name: vim-v81-x64 24 | os: ubuntu-latest 25 | vim_version: 8.1.2414 26 | glibc_version: 2.15 27 | runs-on: ${{matrix.os}} 28 | steps: 29 | - uses: actions/checkout@v1 30 | - name: Download vim 31 | shell: bash 32 | run: | 33 | mkdir -p ~/vim/bin 34 | curl -L https://github.com/vim/vim-appimage/releases/download/v${{matrix.vim_version}}/GVim-v${{matrix.vim_version}}.glibc${{matrix.glibc_version}}-x86_64.AppImage -o ~/vim/bin/vim 35 | chmod u+x ~/vim/bin/vim 36 | - name: Download test runner 37 | shell: bash 38 | run: git clone --depth 1 --branch v1.5.4 --single-branch https://github.com/thinca/vim-themis ~/themis 39 | - name: Run tests 40 | shell: bash 41 | run: | 42 | export PATH=~/vim/bin:$PATH 43 | export PATH=~/themis/bin:$PATH 44 | export THEMIS_VIM=vim 45 | vim --version 46 | themis --reporter spec 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-lexiv 2 | 3 | Small implementation of [lexima.vim](https://github.com/cohama/lexima.vim) 4 | 5 | ## Installation 6 | 7 | VIM 8.1 or above is required. 8 | 9 | For [vim-plug](https://github.com/junegunn/vim-plug) plugin manager: 10 | 11 | ``` 12 | Plug 'mattn/vim-lexiv' 13 | ``` 14 | 15 | ## License 16 | 17 | MIT 18 | 19 | ## Author 20 | 21 | Yasuhiro Matsumoto (a.k.a. mattn) 22 | -------------------------------------------------------------------------------- /autoload/lexiv.vim: -------------------------------------------------------------------------------- 1 | let s:pair = { 2 | \ '"': '"', 3 | \ '''': '''', 4 | \ '`': '`', 5 | \ '{': '}', 6 | \ '[': ']', 7 | \ '(': ')', 8 | \} 9 | let s:stop = ",=:})] \t" 10 | let s:backquote_open_allowlist = ['markdown'] 11 | let s:quote_open_blocklist = {'vim': '"', 'lisp': ''''} 12 | 13 | function! lexiv#backquote_open() abort 14 | if index(s:backquote_open_allowlist, &filetype) != -1 && getline('.') == '``' 15 | return "\U\A`\\```\" 16 | endif 17 | return lexiv#quote_open('`') 18 | endfunction 19 | 20 | function! s:is_completing(lhs) abort 21 | return has_key(b:, 'asyncomplete_refresh_pattern') && a:lhs =~ b:asyncomplete_refresh_pattern 22 | endfunction 23 | 24 | function! s:is_blocklist_case(lhs) abort 25 | return has_key(s:quote_open_blocklist, &filetype) && a:lhs ==# s:quote_open_blocklist[&filetype] 26 | endfunction 27 | 28 | function! s:is_apostrophe(lhs, line, pos) abort 29 | return a:lhs ==# '''' && a:line[a:pos - 2] =~? '\w' 30 | endfunction 31 | 32 | function! s:in_string() abort 33 | return synIDattr(synID(line('.'), col('.')-1, 1), 'name') =~# 'String$' 34 | endfunction 35 | 36 | function! lexiv#quote_open(lhs) abort 37 | if s:is_blocklist_case(a:lhs) 38 | return a:lhs 39 | endif 40 | let l:pos = getpos('.')[2] 41 | let l:line = getline('.') 42 | let l:lhs = l:line[l:pos-2] 43 | let l:rhs = l:line[l:pos-1] 44 | if l:pos ># 1 && l:lhs ==# a:lhs && l:rhs !=# a:lhs 45 | return a:lhs 46 | elseif l:pos ># 1 && l:rhs !=# a:lhs && s:in_string() 47 | return a:lhs 48 | elseif l:pos ># 1 && l:rhs !=# a:lhs && l:rhs =~# '\w' 49 | return a:lhs 50 | elseif (l:rhs =~# '^[,)}]' || l:rhs == '') && !s:is_completing(a:lhs) && !s:is_apostrophe(a:lhs, l:line, l:pos) && !s:in_string() 51 | return a:lhs . a:lhs . "\U\" 52 | elseif l:pos ># 1 && l:line[: l:pos-2] !~? '^[ \t]*$' && l:rhs !=# a:lhs 53 | return a:lhs . a:lhs . "\U\" 54 | elseif l:rhs ==# a:lhs && l:pos <= len(l:line) && l:line[l:pos] !=# a:lhs 55 | return "\U\" 56 | endif 57 | if has_key(s:pair, l:lhs) && s:pair[l:lhs] ==# l:rhs 58 | return a:lhs . a:lhs . "\U\" 59 | endif 60 | return a:lhs 61 | endfunction 62 | 63 | function! lexiv#paren_close(rhs) abort 64 | let l:pos = getpos('.')[2] 65 | let l:line = getline('.') 66 | if l:pos ==# 1 67 | if reg_executing() !=# '' 68 | return a:rhs . "\n" 69 | endif 70 | return a:rhs 71 | elseif l:line[l:pos - 1] !=# a:rhs 72 | if reg_executing() !=# '' 73 | return a:rhs . "\n" 74 | endif 75 | return a:rhs 76 | endif 77 | return "\U\" 78 | endfunction 79 | 80 | function! lexiv#paren_open(lhs) abort 81 | if has_key(b:, 'asyncomplete_refresh_pattern') && a:lhs =~ b:asyncomplete_refresh_pattern 82 | return a:lhs 83 | endif 84 | let l:pos = getpos('.')[2] 85 | let l:line = getline('.') 86 | if l:pos >=# 1 87 | let [l:lhs, l:rhs] = [a:lhs, s:pair[a:lhs]] 88 | if stridx(s:stop, l:line[l:pos - 1]) ==# -1 && len(l:line) !=# l:pos - 1 89 | return a:lhs 90 | endif 91 | endif 92 | return l:lhs . l:rhs . "\U\" 93 | endfunction 94 | 95 | function! lexiv#paren_expand() abort 96 | let l:pos = getpos('.')[2] 97 | let l:line = getline('.') 98 | if l:pos <# 2 || pumvisible() 99 | return "\" 100 | endif 101 | let l:lhs = l:line[l:pos-2] 102 | let l:rhs = l:line[l:pos-1] 103 | if has_key(s:pair, l:lhs) && s:pair[l:lhs] ==# l:rhs 104 | return "\\\O" 105 | endif 106 | return "\" 107 | endfunction 108 | 109 | function! lexiv#paren_delete() abort 110 | let l:pos = getpos('.')[2] 111 | let l:line = getline('.') 112 | if l:pos <# 2 113 | return "\" 114 | endif 115 | let l:lhs = l:line[l:pos-2] 116 | let l:rhs = l:line[l:pos-1] 117 | if has_key(s:pair, l:lhs) && s:pair[l:lhs] ==# l:rhs 118 | return "\U\\\" 119 | endif 120 | return "\" 121 | endfunction 122 | -------------------------------------------------------------------------------- /plugin/lexiv.vim: -------------------------------------------------------------------------------- 1 | inoremap " lexiv#quote_open('"') 2 | inoremap ' lexiv#quote_open("'") 3 | inoremap ` lexiv#backquote_open() 4 | inoremap { lexiv#paren_open('{') 5 | inoremap ( lexiv#paren_open('(') 6 | inoremap [ lexiv#paren_open('[') 7 | inoremap } lexiv#paren_close('}') 8 | inoremap ) lexiv#paren_close(')') 9 | inoremap ] lexiv#paren_close(']') 10 | inoremap lexiv#paren_expand() 11 | inoremap lexiv#paren_delete() 12 | -------------------------------------------------------------------------------- /test/.themisrc: -------------------------------------------------------------------------------- 1 | set encoding=utf-8 2 | call themis#option('recursive', 1) 3 | call themis#helper('command').with(themis#helper('assert')) 4 | -------------------------------------------------------------------------------- /test/lexiv.vimspec: -------------------------------------------------------------------------------- 1 | Describe lexiv 2 | Describe lexiv#paren_open 3 | It should insert pairs 4 | let l:tests = [ 5 | \ ['', 'foo', '(', 'foo()'], 6 | \ ['', 'foo()', '(', 'foo(())'], 7 | \ ['', 'foo)', '(', 'foo())'], 8 | \ ['', 'foo', '(', '(foo'], 9 | \ ['', '', '(', '()'], 10 | \ ['', '()', '(', '(())'], 11 | \ ['', '()', '[', '([])'], 12 | \ ['', '(,)', '[', '([],)'], 13 | \ ['', '(=)', '[', '([]=)'], 14 | \ ['', '(:)', '[', '([]:)'], 15 | \ ['', '(a)', '[', '([a)'], 16 | \ ['', '"', '"', '""'], 17 | \ ['', '"foo"', '"', '"foo"'], 18 | \ ['', '"', '"', '"'], 19 | \ ['', '', '"', '""'], 20 | \ ['', 'BAR=', '"', 'BAR=""'], 21 | \ ['', 'echo ', '"', 'echo ""'], 22 | \ ['vim', '', '"', '"'], 23 | \ ['vim', 'FOO=', '"', 'FOO="'], 24 | \ ['vim', 'echo ', '"', 'echo "'], 25 | \ ['vim', 'echo "bar"', '"', 'echo "bar""'], 26 | \ ['vim', "echo ", "'", "echo ''"], 27 | \ ['vim', "echo 'baz'", "'", "echo 'baz'"], 28 | \ ['', '()', '"', '("")'], 29 | \ ['', '""', '(', '"("'], 30 | \ ['', '""', '{', '"{"'], 31 | \ ['', '""', '[', '"["'], 32 | \ ['', '()', '"', '("")'], 33 | \ ['', '(")', '"', '("")'], 34 | \ ['', '("")', '"', '("")'], 35 | \ ['', '("foo")', '"', '("foo")'], 36 | \ ['', '[]', '"', '[""]'], 37 | \ ['', '', '`', '``'], 38 | \ ['', '``', '`', '```'], 39 | \ ['markdown', '``', '`', "```\n\n```"], 40 | \ ['', 'don', "'", "don''"], 41 | \ ['cpp', '"doo', '"', '"doo"'], 42 | \ ['cpp', 'foo(', '"', 'foo""('], 43 | \ ['cpp', 'foobar', '"', 'foo"bar'], 44 | \] 45 | 46 | for l:test in l:tests 47 | new 48 | inoremap " lexiv#quote_open('"') 49 | call setline(1, l:test[1]) 50 | call search('') 51 | let old_filetype = &filetype 52 | noau let &filetype = l:test[0] 53 | syntax on 54 | exe 'normal' 'ca<' . l:test[2] 55 | noau let &filetype = old_filetype 56 | let l:got = join(getline(1, '$'), "\n") 57 | let l:want = l:test[3] 58 | bw! 59 | Assert Equals(l:got, l:want) 60 | endfor 61 | End 62 | End 63 | End 64 | --------------------------------------------------------------------------------