├── LICENSE
├── README.md
└── plugin
└── gemini.vim
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 KaraMCC
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vim-gemini
2 | vim-gemini is a customizable vim plugin that automatically adds closing, parenthesis, brackets, quotes, and more!
3 |
4 |
5 |
6 | 
7 |
8 | # How do I use it?
9 | Simply type a character that vim-gemini is built to handle, and the plugin will automatically add the closing counterpart.
10 | ```
11 | What is typed: ("|
12 | What appears ("|")
13 | ```
14 |
15 | ```
16 | What is typed: if(foo {|
17 | What appears:
18 | if(foo) {
19 | |
20 | }
21 | ```
22 | Without modifiying any settings, vim-gemini should start working as soon as it's installed.
23 |
24 |
25 |
26 | # Settings
27 | ### How do I define custom pairs for gemini to complete?
28 | Add this to your .vimrc. The first argument is what type of file the matching will take place in. The second argument is a list containing the opening character and the closing character in position 0 and position 1 respectively.
29 | ```
30 | let g:gemini#match_list = {
31 | \'': [['', '']]
32 | \}
33 | ```
34 |
35 |
36 |
37 | You can use regex to expand the use of filetype matches. The example below will allow Gemini to close carets and parentheses, but only when editing an html or xml file.
38 | ```
39 | let g:gemini#match_list = {
40 | \'html\|xml': [['<', '>'], ['(', ')']]
41 | \}
42 | ```
43 |
44 |
45 |
46 | Regex can also be used to define universal matches. The example below will automatically close curly brackets and parentheses when editing any file.
47 | ```
48 | let g:gemini#match_list = {
49 | \'.*': [['{', '}'], ['(', ')']]
50 | \}
51 | ```
52 |
53 |
54 |
55 | ### How do I change the default matches that Gemini uses?
56 | The plugin uses a list of default matches which can be edited by the user by modifying the global variable ```g:gemini#override_matches```. In the example below, the default matches are changed so that only curly brackets will be matched.
57 | ```
58 | let g:gemini#override_matches = {
59 | \'.*': [['{', '}']],
60 | \}
61 | ```
62 |
63 |
64 |
65 | ### How do I allow Gemini to match characters in comments?
66 | By default, Gemini will detect when your cursor is in a commented out area of text, and not complete the matches. You can let the plugin match characters simply by adding this to your .vimrc.
67 | ```
68 | let g:gemini#match_in_comment = 1
69 | ```
70 |
71 |
72 |
73 | ### How do I disable matching when the cursor is imediately before a character?
74 | By default, Gemini will append matching characters when the cursor is positioned immediately before a non-whitespace characer. You can disable matching in that scenario by putting ```let g:gemini#cozy_matching = 0``` in your .vimrc. This will make sure Gemini only appends matching characters when there is extra room around the cursor.
75 |
76 |
77 |
78 | # Installation
79 | #### With [pathogen](https://github.com/tpope/vim-pathogen):
80 | ```
81 | cd ~/.vim/bundle && \
82 | git clone https://github.com/KaraMCC/vim-gemini.git
83 | ```
84 |
85 | #### With [vim-plug](https://github.com/junegunn/vim-plug):
86 | ```
87 | Plug 'KaraMCC/vim-gemini'
88 | ```
89 |
90 | #### With [Vundle](https://github.com/VundleVim/Vundle.vim):
91 | ```
92 | Plugin 'KaraMCC/vim-gemini'
93 | ```
94 |
--------------------------------------------------------------------------------
/plugin/gemini.vim:
--------------------------------------------------------------------------------
1 | if exists('g:loaded_gemini_plugin')
2 | finish
3 | endif
4 | let g:loaded_gemini_plugin = 1
5 |
6 | " Define default matches
7 | let s:default_matches = {
8 | \'.*': [['(', ')'], ['{', '}'], ['[', ']'], ['"', '"']],
9 | \'.*python\|.*php\|.*c\|.*cpp\|.*cs\|.*sh\|.*html\|.*xml\|.*vim\|.*perl\|.*rust\|.*java\|.*javascript': [["'", "'"]],
10 | \'.*html\|xml': [['<', '>']],
11 | \'r\|go\|sh\|javascript': [['`', '`']],
12 | \}
13 | " Allow user to override default matches
14 | let s:base_match_list = get(g:, 'gemini#override_matches', s:default_matches)
15 |
16 | " Get user settings
17 | let g:gemini#match_list = get(g:, 'gemini#match_list', {})
18 | let g:gemini#match_in_comment = get(g:, 'gemini#match_in_comment', 0)
19 | let g:gemini#cozy_matching = get(g:, 'gemini#cozy_matching', 1)
20 |
21 | augroup create_gemini_mappings
22 | autocmd!
23 | autocmd BufEnter * call CreateMappings()
24 | augroup END
25 |
26 | function! s:CreateMatchList()
27 | let b:match_chars = []
28 | " Get full list of potential matches by combining base matches and matches defined in user settings
29 | let l:potential_matches = extend(s:base_match_list, g:gemini#match_list)
30 | " Loop through all potential matches, and add them if the regex matches
31 | for key in keys(l:potential_matches)
32 | if &filetype =~? key
33 | let b:match_chars += l:potential_matches[key]
34 | endif
35 | endfor
36 | endfunction
37 |
38 | function! s:CreateMappings()
39 | if !exists('b:match_chars')
40 | call s:CreateMatchList()
41 | endif
42 | " Loop through characters to match
43 | for matches in b:match_chars
44 | if matches[0] !=# matches[1]
45 | exec printf('inoremap %s OnOpeningCharacter("\%s","\%s")', matches[0], matches[0], matches[1])
46 | exec printf('inoremap %s OnClosingCharacter("\%s")', matches[1], matches[1])
47 | else
48 | " A different function is required if the opening and closing char are the same
49 | exec printf('inoremap %s OnTypedDuplicate("\%s")', matches[0], matches[1])
50 | endif
51 | endfor
52 | inoremap TryDeletePair()
53 | endfunction
54 |
55 | function! s:OnOpeningCharacter(open, close)
56 | if g:gemini#match_in_comment || !s:IsComment()
57 | " Get the string index of a:open, starting at the cursor's position
58 | let l:open_idx = stridx(getline('.'), a:open, 0)
59 | " Do the same for a:close
60 | let l:close_idx = stridx(getline('.'), a:close, 0)
61 | if l:open_idx == l:close_idx || l:open_idx < l:close_idx && l:open_idx != -1
62 | " Only match if the cursor is next to whitespace --
63 | " disregard if cozy matching is enabled
64 | if g:gemini#cozy_matching || getline('.')[col('.') - 1] =~? '\s' || col('.') == col('$')
65 | " Add both the characters, then move the cursor to the left
66 | return a:open . a:close . "\"
67 | endif
68 | endif
69 | endif
70 | return a:open
71 | endfunction
72 |
73 | function! s:OnClosingCharacter(close)
74 | " If pair of characters is already closed, move right
75 | if getline('.')[col('.')-1] ==# a:close
76 | return "\"
77 | else
78 | return a:close
79 | endif
80 | endfunction
81 |
82 | function! s:OnTypedDuplicate(char)
83 | " Don't repeat if in comment, unless user has enabled it
84 | if g:gemini#cozy_matching || getline('.')[col('.') - 1] =~? '\s' || col('.') == col('$')
85 | " If the specified character is adjacent to the cursor, do not repeat
86 | if g:gemini#match_in_comment || !s:IsComment()
87 | if stridx(getline('.'), a:char, col('.') - 2) == -1
88 | " Don't close if an even number of a:char exists on the line
89 | if count(getline('.'), a:char) % 2 == 0
90 | return repeat(a:char, 2) . "\"
91 | endif
92 | " If cursor is already on the specified character, move right
93 | elseif getline('.')[col('.') - 1] ==# a:char
94 | return "\"
95 | endif
96 | endif
97 | endif
98 | return a:char
99 | endfunction
100 |
101 | " If the opening character is deleted, also delete the closing one
102 | function! s:TryDeletePair()
103 | for chars in b:match_chars
104 | if getline('.')[col('.')-2] ==# chars[0] && getline('.')[col('.')-1] ==# chars[1]
105 | return "\\\"
106 | endif
107 | endfor
108 | return "\"
109 | endfunction
110 |
111 | function! s:IsComment()
112 | " Return 1 if the name of the syntax id under cursor is 'Comment'
113 | return synIDattr(synIDtrans(synID(line('.'), col('.') - 1, 1)), 'name') ==? 'Comment'
114 | endfunction
115 |
--------------------------------------------------------------------------------