├── .gitignore ├── .travis.yml ├── Gemfile ├── Rakefile ├── VimFlavor ├── autoload └── smartinput.vim ├── doc └── smartinput.txt ├── plugin └── smartinput.vim └── t ├── api.vim ├── beep-on-empty-line.t ├── beep-on-empty-line.vim ├── break-undo.vim ├── map_trigger_keys.vim ├── misc.vim ├── proper-initialization.vim ├── startup-no-default-key-mappings.vim └── startup-preserving-existing-key-mappings.vim /.gitignore: -------------------------------------------------------------------------------- 1 | .vim-flavor 2 | Gemfile.lock 3 | VimFlavor.lock 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: vim 2 | cache: 3 | directories: 4 | - $HOME/.rvm 5 | - $HOME/.vvm 6 | before_install: 7 | - curl https://raw.githubusercontent.com/kana/vim-version-manager/master/bin/vvm | python - setup; true 8 | - source ~/.vvm/etc/login 9 | - vvm update_itself 10 | - vvm use vimorg--v8.0.1529 --install --with-features=huge 11 | - 12 | - rvm use 2.5.0 --install --binary --fuzzy 13 | script: rake ci 14 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'vim-flavor', '~> 2.1' 4 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | 3 | task :ci => [:dump, :test] 4 | 5 | task :dump do 6 | sh 'vim --version' 7 | end 8 | 9 | task :test do 10 | sh 'bundle exec vim-flavor test' 11 | end 12 | -------------------------------------------------------------------------------- /VimFlavor: -------------------------------------------------------------------------------- 1 | # No dependencies. 2 | -------------------------------------------------------------------------------- /autoload/smartinput.vim: -------------------------------------------------------------------------------- 1 | " smartinput - Provide smart input assistant 2 | " Version: 0.1.0 3 | " Copyright (C) 2012-2018-2018 Kana Natsuno 4 | " License: MIT license {{{ 5 | " Permission is hereby granted, free of charge, to any person obtaining 6 | " a copy of this software and associated documentation files (the 7 | " "Software"), to deal in the Software without restriction, including 8 | " without limitation the rights to use, copy, modify, merge, publish, 9 | " distribute, sublicense, and/or sell copies of the Software, and to 10 | " permit persons to whom the Software is furnished to do so, subject to 11 | " the following conditions: 12 | " 13 | " The above copyright notice and this permission notice shall be included 14 | " in all copies or substantial portions of the Software. 15 | " 16 | " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | " OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | " IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | " CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | " TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | " SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | " }}} 24 | " Naming guidelines "{{{1 25 | " Rules "{{{2 26 | " 27 | " "urule" stands for "Unnormalized RULE". 28 | " urules are rules written by users. 29 | " Optional items may be omitted from urules. 30 | " 31 | " "nrule" stands for "Normalized RULE". 32 | " nrules are rules completed with all optional items and internal items. 33 | " 34 | " "snrule" stands for "SemiNormalized RULE". 35 | " snrules are mostly same as nrules, the only one difference is that 36 | " "priority" items may be omitted from snrules. 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | " Variables "{{{1 46 | let s:available_nrules = [] "{{{2 47 | " :: [NRule] -- it is ALWAYS sorted by priority in descending order. 48 | 49 | 50 | 51 | 52 | "{{{2 53 | 54 | 55 | 56 | 57 | " Interface "{{{1 58 | function! smartinput#clear_rules() "{{{2 59 | let s:available_nrules = [] 60 | endfunction 61 | 62 | 63 | 64 | 65 | function! smartinput#define_default_rules() "{{{2 66 | " urules "{{{ 67 | let urules = {} 68 | let urules.names = [] 69 | let urules.table = {} 70 | function! urules.add(name, urules) 71 | call add(self.names, a:name) 72 | let self.table[a:name] = a:urules 73 | endfunction 74 | if get(g:, 'smartinput_break_undo', 0) || v:version < 800 75 | let left = '' 76 | let right = '' 77 | else 78 | let left = 'U' 79 | let right = 'U' 80 | endif 81 | call urules.add('()', [ 82 | \ {'at': '\%#', 'char': '(', 'input': '()'.left}, 83 | \ {'at': '\%#\_s*)', 'char': ')', 'input': '=smartinput#_leave_block('')'')'.right}, 84 | \ {'at': '(\%#)', 'char': '', 'input': ''}, 85 | \ {'at': '()\%#', 'char': '', 'input': ''}, 86 | \ {'at': '\\\%#', 'char': '(', 'input': '('}, 87 | \ {'at': '(\%#)', 'char': '', 'input': '"_S'}, 88 | \ ]) 89 | call urules.add('[]', [ 90 | \ {'at': '\%#', 'char': '[', 'input': '[]'.left}, 91 | \ {'at': '\%#\_s*\]', 'char': ']', 'input': '=smartinput#_leave_block('']'')'.right}, 92 | \ {'at': '\[\%#\]', 'char': '', 'input': ''}, 93 | \ {'at': '\[\]\%#', 'char': '', 'input': ''}, 94 | \ {'at': '\\\%#', 'char': '[', 'input': '['}, 95 | \ ]) 96 | call urules.add('{}', [ 97 | \ {'at': '\%#', 'char': '{', 'input': '{}'.left}, 98 | \ {'at': '\%#\_s*}', 'char': '}', 'input': '=smartinput#_leave_block(''}'')'.right}, 99 | \ {'at': '{\%#}', 'char': '', 'input': ''}, 100 | \ {'at': '{}\%#', 'char': '', 'input': ''}, 101 | \ {'at': '\\\%#', 'char': '{', 'input': '{'}, 102 | \ {'at': '{\%#}', 'char': '', 'input': '"_S'}, 103 | \ ]) 104 | call urules.add('''''', [ 105 | \ {'at': '\%#', 'char': '''', 'input': ''''''.left}, 106 | \ {'at': '\%#''\ze', 'char': '''', 'input': ''.right}, 107 | \ {'at': '''\%#''', 'char': '', 'input': ''}, 108 | \ {'at': '''''\%#', 'char': '', 'input': ''}, 109 | \ {'at': '\\\%#\ze', 'char': '''', 'input': ''''}, 110 | \ ]) 111 | " Though strong quote is a useful feature and it is supported in several 112 | " languages, \ is usually used to escape next charcters in most languages. 113 | " So that rules for strong quote are written as additional ones for specific 114 | " 'filetype's which override the default behavior. 115 | call urules.add(''''' as strong quote', [ 116 | \ {'at': '\%#''', 'char': '''', 'input': ''.right}, 117 | \ ]) 118 | call urules.add('''''''', [ 119 | \ {'at': '''''\%#', 'char': '''', 'input': ''''''''''.left.left.left}, 120 | \ {'at': '\%#''''''\ze', 'char': '''', 'input': ''.right.right.right}, 121 | \ {'at': '''''''\%#''''''', 'char': '', 'input': ''}, 122 | \ {'at': '''''''''''''\%#', 'char': '', 'input': ''}, 123 | \ ]) 124 | call urules.add('""', [ 125 | \ {'at': '\%#', 'char': '"', 'input': '""'.left}, 126 | \ {'at': '\%#"', 'char': '"', 'input': ''.right}, 127 | \ {'at': '"\%#"', 'char': '', 'input': ''}, 128 | \ {'at': '""\%#', 'char': '', 'input': ''}, 129 | \ {'at': '\\\%#', 'char': '"', 'input': '"'}, 130 | \ ]) 131 | call urules.add('"""', [ 132 | \ {'at': '""\%#', 'char': '"', 'input': '""""'.left.left.left}, 133 | \ {'at': '\%#"""', 'char': '"', 'input': ''.right.right.right}, 134 | \ {'at': '"""\%#"""', 'char': '', 'input': ''}, 135 | \ {'at': '""""""\%#', 'char': '', 'input': ''}, 136 | \ ]) 137 | call urules.add('``', [ 138 | \ {'at': '\%#', 'char': '`', 'input': '``'.left}, 139 | \ {'at': '\%#`', 'char': '`', 'input': ''.right}, 140 | \ {'at': '`\%#`', 'char': '', 'input': ''}, 141 | \ {'at': '``\%#', 'char': '', 'input': ''}, 142 | \ {'at': '\\\%#', 'char': '`', 'input': '`'}, 143 | \ ]) 144 | call urules.add('```', [ 145 | \ {'at': '``\%#', 'char': '`', 'input': '````'.left.left.left}, 146 | \ {'at': '\%#```', 'char': '`', 'input': ''.right.right.right}, 147 | \ {'at': '```\%#```', 'char': '', 'input': ''}, 148 | \ {'at': '``````\%#', 'char': '', 'input': ''}, 149 | \ ]) 150 | call urules.add('English', [ 151 | \ {'at': '\w\%#', 'char': '''', 'input': ''''}, 152 | \ ]) 153 | call urules.add('Lisp quote', [ 154 | \ {'at': '\%#', 'char': '''', 'input': ''''}, 155 | \ {'at': '\%#', 'char': '''', 'input': ''''''.left.'', 156 | \ 'syntax': ['Constant']}, 157 | \ ]) 158 | " Unfortunately, the space beyond the end of a comment line is not 159 | " highlighted as 'Comment'. So that it is necessary to define one more rule 160 | " to cover the edge case with only 'at'. 161 | call urules.add('Python string', [ 162 | \ {'at': '\v\c<([bu]|[bu]?r)>%#', 'char': '''', 'input': ''''''.left}, 163 | \ {'at': '\v\c<([bu]|[bu]?r)>%#', 'char': '''', 'input': '''', 164 | \ 'syntax': ['Comment', 'Constant']}, 165 | \ {'at': '\v\c\#.*<([bu]|[bu]?r)>%#$', 'char': '''', 'input': ''''}, 166 | \ ]) 167 | call urules.add('Vim script comment', [ 168 | \ {'at': '^\s*\%#', 'char': '"', 'input': '"'}, 169 | \ ]) 170 | "}}} 171 | 172 | " ft_urule_sets_table... "{{{ 173 | let ft_urule_sets_table = { 174 | \ '*': [ 175 | \ urules.table['()'], 176 | \ urules.table['[]'], 177 | \ urules.table['{}'], 178 | \ urules.table[''''''], 179 | \ urules.table[''''''''], 180 | \ urules.table['""'], 181 | \ urules.table['"""'], 182 | \ urules.table['``'], 183 | \ urules.table['```'], 184 | \ urules.table['English'], 185 | \ ], 186 | \ 'clojure': [ 187 | \ urules.table['Lisp quote'], 188 | \ ], 189 | \ 'csh': [ 190 | \ urules.table[''''' as strong quote'], 191 | \ ], 192 | \ 'lisp': [ 193 | \ urules.table['Lisp quote'], 194 | \ ], 195 | \ 'perl': [ 196 | \ urules.table[''''' as strong quote'], 197 | \ ], 198 | \ 'python': [ 199 | \ urules.table['Python string'], 200 | \ ], 201 | \ 'ruby': [ 202 | \ urules.table[''''' as strong quote'], 203 | \ ], 204 | \ 'scheme': [ 205 | \ urules.table['Lisp quote'], 206 | \ ], 207 | \ 'sh': [ 208 | \ urules.table[''''' as strong quote'], 209 | \ ], 210 | \ 'tcsh': [ 211 | \ urules.table[''''' as strong quote'], 212 | \ ], 213 | \ 'vim': [ 214 | \ urules.table[''''' as strong quote'], 215 | \ urules.table['Vim script comment'], 216 | \ ], 217 | \ 'zsh': [ 218 | \ urules.table[''''' as strong quote'], 219 | \ ], 220 | \ } 221 | "}}} 222 | 223 | for urule_set in ft_urule_sets_table['*'] 224 | for urule in urule_set 225 | call smartinput#define_rule(urule) 226 | endfor 227 | endfor 228 | 229 | let overlaied_urules = {} 230 | let overlaied_urules.pairs = [] " [(URule, [FileType])] 231 | function! overlaied_urules.add(urule, ft) 232 | for [urule, fts] in self.pairs 233 | if urule is a:urule 234 | call add(fts, a:ft) 235 | return 236 | endif 237 | endfor 238 | call add(self.pairs, [a:urule, [a:ft]]) 239 | endfunction 240 | for ft in filter(keys(ft_urule_sets_table), 'v:val != "*"') 241 | for urule_set in ft_urule_sets_table[ft] 242 | for urule in urule_set 243 | call overlaied_urules.add(urule, ft) 244 | endfor 245 | endfor 246 | endfor 247 | for [urule, fts] in overlaied_urules.pairs 248 | let completed_urule = copy(urule) 249 | let completed_urule.filetype = fts 250 | call smartinput#define_rule(completed_urule) 251 | endfor 252 | 253 | " Add more useful rules? 254 | endfunction 255 | 256 | function! s:_operator_key_from(operator_name) 257 | let k = a:operator_name 258 | let k = substitute(k, '\V<', '', 'g') 259 | let k = substitute(k, '\V|', '', 'g') 260 | return k 261 | endfunction 262 | 263 | function! s:_operator_pattern_from(operator_name) 264 | let k = a:operator_name 265 | return k 266 | endfunction 267 | 268 | function! smartinput#_leave_block(end_char) 269 | " NB: Originally was used to execute search(), but in 270 | " Visual-block Insert acts as if a, so visually selected lines will be 271 | " updated and the current mode will be shifted to Insert mode. It means 272 | " that there is no timing to execute a Normal mode command. Therefore we 273 | " have to use = instead. 274 | call search(a:end_char, 'cW') 275 | return '' 276 | endfunction 277 | 278 | 279 | 280 | 281 | function! smartinput#define_rule(urule) "{{{2 282 | let nrule = s:normalize_rule(a:urule) 283 | call s:insert_or_replace_a_rule(s:available_nrules, nrule) 284 | endfunction 285 | 286 | 287 | 288 | 289 | function! smartinput#map_to_trigger(mode, lhs, rhs_char, rhs_fallback) "{{{2 290 | " According to :help 'autoindent' -- 291 | " 292 | " > Copy indent from current line when starting a new line 293 | " > (typing in Insert mode or when using the "o" or "O" command). 294 | " > If you do not type anything on the new line except or CTRL-D 295 | " > and then type , CTRL-O or , the indent is deleted again. 296 | " 297 | " So that a:rhs_fallback MUST be mapped from a:lhs without leaving from 298 | " Insert mode to keep the behavior on automatic indentation when 299 | " a:rhs_fallback == '', 300 | let char_expr = s:_encode_for_map_char_expr(a:rhs_char) 301 | let fallback_expr = s:_encode_for_map_char_expr(a:rhs_fallback) 302 | execute printf('%snoremap %s %s _trigger_or_fallback(%s, %s)', 303 | \ a:mode, 304 | \ '