├── .gitignore ├── tests ├── teardown.sh ├── testcases │ ├── bug-tracker.test.sh │ ├── table-corner-cases.test.sh │ ├── bug-semicolon.test.sh │ ├── should-insert-hanging-close-brace.test.sh │ ├── should-add-pair-at-end-of-string.test.sh │ ├── bug-closing-pairs.test.sh │ ├── should-not-add-single-quote-after-work.test.sh │ ├── bug-pairs-at-the-start-of-line.test.sh │ ├── bug-remove-closing-curly-brace.test.sh │ ├── should-not-delete-whole-line.test.sh │ ├── bug-end-of-line.test.sh │ ├── should-surround-quoted-strings.test.sh │ ├── should-insert-pair-before-semicolon.test.sh │ ├── enter-after-open-pairs-default-settings.test.sh │ ├── bug-end-of-line-with-bspace.test.sh │ ├── enter-between-letters.test.sh │ ├── should-remove-matching-pair-on-backspace-at-the-start-of-line.test.sh │ ├── should-correctly-surround-word-before-block-start.test.sh │ ├── should-not-add-quotes-around-nested-function-call.test.sh │ ├── should-not-panic-on-simple-brace-close-at-the-beginning-of-the-line.test.sh │ ├── should-surround-till-colon.test.sh │ ├── should-remove-matching-pair-on-backspace-in-the-middle-of-line.test.sh │ ├── should-surround-at-end-of-the-block.test.sh │ ├── bug-block-insert-parenthesis-does-not-work.test.sh │ ├── closing-pairs-with-space.test.sh │ ├── enter-bracket-and-letter-and-indentation.test.sh │ ├── should-surround-till-brace-in-braces.test.sh │ ├── closing-pairs-with-dot.test.sh │ ├── enter-between-letters-two-times.test.sh │ ├── should-not-crash-on-end-of-the-code-block-if-changed.test.sh │ ├── should-properly-correct-parenthesis-at-end-of-line.test.sh │ ├── should-surround-second-function-argument.test.sh │ ├── enter-between-brackets-with-indentation.test.sh │ ├── should-not-surround-after-brace-if-text-follows.test.sh │ ├── enter-between-brackets-and-letters-after-closing-bracket.test.sh │ ├── should-not-crash-on-end-of-the-code-block.test.sh │ ├── go-should-properly-surround-map-string-interface.test.sh │ ├── enter-after-bracket-with-body.test.sh │ ├── enter-between-letters-two-times-with-indetation.test.sh │ ├── should-correct-added-pair-in-same-line-before-insertion.test.sh │ ├── enter-after-open-pairs-et-sw3.test.sh │ ├── should-not-correct-added-pair-in-same-line-after-insertion-if-another-enclosure-started.test.sh │ ├── should-correct-added-pair-in-same-line-at-end-of-line.test.sh │ ├── should-correct-added-pair-in-same-line-after-insertion.test.sh │ ├── should-surround-first-function-argument.test.sh │ └── table-quotes.test.sh └── setup.sh ├── .gitmodules ├── autosurround.vimrc ├── run_tests ├── plugin └── autosurround.vim ├── README.md └── pythonx └── autosurround.py /.gitignore: -------------------------------------------------------------------------------- 1 | .ropeproject/ 2 | *.pyc 3 | /vendor 4 | /.last-testcase 5 | -------------------------------------------------------------------------------- /tests/teardown.sh: -------------------------------------------------------------------------------- 1 | tests:eval vim-tests:get-messages "$_vim_session" 2 | 3 | vim-tests:end-silent "$_vim_session" 4 | -------------------------------------------------------------------------------- /tests/testcases/bug-tracker.test.sh: -------------------------------------------------------------------------------- 1 | :testcase <" "escape" \ 4 | "0fca(" 5 | 6 | tests:eval tmux:cat-screen "$_vim_session" 7 | 8 | vim-tests:write-file "$_vim_session" "result" 9 | 10 | tests:assert-no-diff "result" < 12 | EXPECTED 13 | -------------------------------------------------------------------------------- /tests/testcases/should-surround-quoted-strings.test.sh: -------------------------------------------------------------------------------- 1 | vim-tests:type "$_vim_session" \ 2 | 'i"abc"' "escape" \ 3 | 'I(' "escape" 4 | 5 | tests:eval tmux:cat-screen "$_vim_session" 6 | 7 | vim-tests:write-file "$_vim_session" "result" 8 | 9 | tests:assert-no-diff "result" <" "escape" \ 4 | "0fca(" "bspace" "|" 5 | 6 | tests:eval tmux:cat-screen "$_vim_session" 7 | 8 | vim-tests:write-file "$_vim_session" "result" 9 | 10 | tests:assert-no-diff "result" < 12 | EXPECTED 13 | -------------------------------------------------------------------------------- /tests/testcases/enter-between-letters.test.sh: -------------------------------------------------------------------------------- 1 | vim-tests:type "$_vim_session" \ 2 | "i" \ 3 | "ab" "escape" "i" "enter" \ 4 | "|" \ 5 | "escape" 6 | 7 | tests:eval tmux:cat-screen "$_vim_session" 8 | 9 | vim-tests:write-file "$_vim_session" "result" 10 | 11 | tests:assert-no-diff "result" < table 12 | csplit \ 13 | --prefix=chunk \ 14 | --suppress-matched table \ 15 | '/^@$/' '{*}' >/dev/null 16 | while read cmd; do 17 | vim-tests:type "$_vim_session" "$cmd" 18 | done < chunk00 19 | tests:eval tmux:cat-screen "$_vim_session" <&- 20 | 21 | vim-tests:write-file "$_vim_session" "result" 22 | tests:assert-no-diff "result" < chunk01 23 | 24 | vim-tests:type "$_vim_session" "escape" "escape" "ggVGdd" "escape" 25 | rm -rf chunk* 26 | cp /dev/null /tmp/autosurround.log 27 | } 28 | -------------------------------------------------------------------------------- /plugin/autosurround.vim: -------------------------------------------------------------------------------- 1 | python3 import vim, autosurround 2 | 3 | let g:autosurround_enquote_filetypes_exclude = get( 4 | \ g:, 'autosurround_enquote_filetypes_exclude', ['markdown', '']) 5 | 6 | fun! AutoSurroundInitMappings() 7 | if index(g:autosurround_enquote_filetypes_exclude, &ft) < 0 8 | inoremap " :python3 autosurround.enquote('"') 9 | inoremap ' :python3 autosurround.enquote("'") 10 | endif 11 | 12 | inoremap ( :python3 autosurround.surround('(', ')') 13 | inoremap ) :python3 autosurround.correct_pair('(', ')') 14 | 15 | inoremap [ :python3 autosurround.surround('[', ']') 16 | inoremap ] :python3 autosurround.correct_pair('[', ']') 17 | 18 | inoremap { :python3 autosurround.surround('{', '}') 19 | inoremap } :python3 autosurround.correct_pair('{', '}') 20 | 21 | inoremap :python3 autosurround.remove_pair() 22 | inoremap :python3 autosurround.insert_new_line() 23 | endfun! 24 | 25 | augroup autosurround 26 | au! 27 | au CursorMovedI * python3 autosurround.clean_current_pairs() 28 | au BufEnter,FileType * call AutoSurroundInitMappings() 29 | augroup END 30 | 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Plugin will automatically add enclosing `)` (or any other) at appropriate place to be. 2 | 3 | ![gif](https://cloud.githubusercontent.com/assets/674812/10417889/f530e936-703a-11e5-8f77-2b7f6fe23191.gif) 4 | 5 | Currently, following cases are supported: 6 | 7 | * adding pair after call-like construction: 8 | ``` 9 | |something() type: test( -> 10 | test(|something()) 11 | ``` 12 | 13 | * adding pair after string: 14 | ``` 15 | |"blah" type: test( -> 16 | test(|"blah") 17 | ``` 18 | 19 | * adding pair after argument: 20 | ``` 21 | something(|arg1, arg2) type: test( -> 22 | something(test(|arg1), arg2) 23 | ``` 24 | 25 | ``` 26 | something(arg1, |arg2) type: test( -> 27 | something(arg1, test(arg2)) 28 | ``` 29 | 30 | * adding pairs in conditionals: 31 | ``` 32 | if |blah != nil type test( -> 33 | if test(blah) != nil 34 | ``` 35 | 36 | * autocorrection: 37 | ``` 38 | something(|arg1, arg2) type: test( -> 39 | something(test(|arg1), arg2) move cursor after last ) and type ) -> 40 | something(test(arg1), arg2)| 41 | something(test(arg1, arg2))| 42 | ``` 43 | 44 | # Installation & Usage 45 | 46 | ```viml 47 | Plug 'vim-autosurround' 48 | ``` 49 | 50 | Plugin provides only python API. 51 | 52 | # Extension 53 | 54 | Plugin provides API, which can be used to extend surround logic: 55 | 56 | * `index = autosurround.register_finder(callback)`, `callback` is a function 57 | of one argument `cursor`, which is `vim.current.window.cursor`. 58 | 59 | `callback` should return tuple `(line, column)` with position, which will be 60 | used for inserting pair character or `None`, if `callback` is not able to 61 | find position. 62 | 63 | `index` can be used for `unregister_finder(index)`. 64 | 65 | * `autosurround.unregister_finder(index)` will remove previously added 66 | callback. 67 | -------------------------------------------------------------------------------- /pythonx/autosurround.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | from contextlib import contextmanager 4 | import vim 5 | import re 6 | 7 | import logging as log 8 | 9 | _current_pairs = {} 10 | 11 | RE_CLOSING_PAIRS = r'^[)}"\]]+$' 12 | RE_OPENING_PAIRS = r'^[\(\{"\[]+$' 13 | 14 | PAIRS = { 15 | '(': ')', 16 | '{': '}', 17 | '[': ']', 18 | '"': '"', 19 | "'": "'" 20 | } 21 | 22 | BRACKETS = { 23 | '(': ')', 24 | '{': '}', 25 | '[': ']', 26 | } 27 | 28 | 29 | def enable_debug_log(): 30 | log.basicConfig( 31 | filename='/tmp/autosurround.log', 32 | filemode='w', 33 | format='%(levelname)s - %(message)s', 34 | level=log.DEBUG 35 | ) 36 | 37 | 38 | def insert_new_line(): 39 | cursor = _get_cursor() 40 | 41 | indent = vim.Function('indent')(cursor[0]) 42 | tabwidth = vim.Function('shiftwidth')() 43 | 44 | in_brackets = _is_cursor_between_brackets() 45 | 46 | expandtab = int(vim.eval('&expandtab')) 47 | if not expandtab: 48 | symbol = '\t' 49 | shift = indent/tabwidth 50 | next_shift = int(shift) 51 | if in_brackets: 52 | next_shift += 1 53 | else: 54 | symbol = ' ' 55 | shift = indent 56 | next_shift = shift 57 | if in_brackets: 58 | next_shift += tabwidth 59 | 60 | if in_brackets and in_brackets[1] == None: 61 | _insert_new_line_at_cursor(1, int(next_shift)*symbol) 62 | _set_cursor(cursor[0]+1, int(next_shift)) 63 | return 64 | 65 | if not in_brackets: 66 | _insert_new_line_at_cursor(1, int(shift)*symbol) 67 | _set_cursor(cursor[0]+1, int(shift)) 68 | return 69 | 70 | _insert_new_line_at_cursor(2, int(shift)*symbol) 71 | vim.current.buffer[cursor[0]] = (int(next_shift) * symbol) 72 | _set_cursor(cursor[0]+1, int(next_shift)) 73 | 74 | 75 | def _is_cursor_between_brackets(): 76 | cursor = _get_cursor() 77 | line = vim.current.buffer[cursor[0] - 1] 78 | if cursor[1] < 1: 79 | return False 80 | 81 | open_bracket = line[cursor[1]-1] 82 | if open_bracket not in BRACKETS: 83 | return False 84 | 85 | close_bracket = BRACKETS[open_bracket] 86 | if len(line) > cursor[1]: 87 | cursor_char = line[cursor[1]] 88 | if cursor_char == close_bracket: 89 | return (open_bracket, close_bracket) 90 | 91 | return (open_bracket, None) 92 | 93 | 94 | def enquote(char): 95 | cursor = _get_cursor() 96 | 97 | line = cursor[0] - 1 98 | column = cursor[1] 99 | 100 | text = vim.current.buffer[line] 101 | text_before = text[:column] 102 | text_after = text[column:] 103 | 104 | if len(text_before) > 0 and text_before[-1]== "\\": 105 | if len(text_before) > 1 and text_before[-2] != "\\": 106 | _insert_at_cursor(char) 107 | return 108 | 109 | count_before = count_quotes(text_before, char) 110 | count_after = count_quotes(text_after, char) 111 | 112 | if count_before == 0 and count_after == 0: 113 | surround(char, char) 114 | return 115 | 116 | if count_after == 0 or count_before == 0: 117 | surround(char, char) 118 | return 119 | 120 | mod = (count_before + count_after) % 2 121 | if mod == 0: 122 | if len(text_after) > 0 and text_after[0] == char: 123 | _move_cursor_relative(0, 1) 124 | return 125 | 126 | if not surround(char, char): 127 | with _restore_cursor(): 128 | _insert_at_cursor(char) 129 | 130 | 131 | def count_quotes(text, quote): 132 | escape = False 133 | count = 0 134 | for char in text: 135 | if char == "\\": 136 | escape = not escape 137 | continue 138 | if char == quote: 139 | count += 1 140 | return count 141 | 142 | 143 | def surround(open_pair, close_pair): 144 | cursor = _get_cursor() 145 | 146 | try: 147 | end_pos = find_enclosure(cursor) 148 | if not end_pos: 149 | return False 150 | finally: 151 | _insert_at_cursor(open_pair) 152 | 153 | vim.command('let &undolevels = &undolevels') 154 | 155 | # can't be omit moving cursor here? we could use vim.current.buffer 156 | with _restore_cursor(): 157 | _set_cursor(end_pos[0], end_pos[1]) 158 | _insert_at_cursor(close_pair) 159 | 160 | buffer = vim.current.buffer 161 | _current_pairs[(buffer.number, cursor, close_pair)] = ( 162 | end_pos[0], 163 | end_pos[1] 164 | ) 165 | 166 | return True 167 | 168 | 169 | def find_enclosure(cursor): 170 | for strategy in enclosing_strategies: 171 | if strategy is None: 172 | continue 173 | 174 | pos = strategy(cursor) 175 | if pos is not None: 176 | return pos 177 | 178 | 179 | def correct_inserted_pair(open_pair, close_pair): 180 | buffer= vim.current.buffer 181 | cursor = _get_cursor() 182 | 183 | text = buffer[cursor[0]-1] 184 | if cursor[1]-1 > 0: 185 | if buffer[cursor[0]-1][cursor[1]-1] == close_pair: 186 | return False 187 | 188 | open_pair_pos = vim.Function('searchpairpos')( 189 | _escape_open_pair(open_pair), 190 | "", 191 | close_pair, 192 | "nb", 193 | ) 194 | 195 | if not open_pair_pos or (open_pair_pos[0] == 0 and open_pair_pos[0] == 0): 196 | return False 197 | 198 | close_pair_pos = None 199 | with _restore_cursor(): 200 | _set_cursor(open_pair_pos[0], open_pair_pos[1]-1) 201 | cursor_before = _get_cursor() 202 | vim.command('normal! %') 203 | cursor_after = _get_cursor() 204 | close_pair_pos = _get_cursor() 205 | 206 | if cursor_before == cursor_after: 207 | return False 208 | 209 | if close_pair_pos[0] == cursor[0]: 210 | if close_pair_pos[1] == cursor[1]: 211 | return False 212 | 213 | if close_pair_pos[0] == open_pair_pos[0]: 214 | if close_pair_pos[1] == open_pair_pos[1]: 215 | return False 216 | 217 | if not close_pair_pos: 218 | return False 219 | 220 | if close_pair_pos[0] != vim.current.window.cursor[0]: 221 | return False 222 | 223 | moved = _delete_at(close_pair_pos, 1) 224 | _insert_at_cursor(close_pair) 225 | 226 | return True 227 | 228 | 229 | def correct_current_pair(open_pair, close_pair): 230 | buffer = vim.current.buffer 231 | 232 | for pair in _current_pairs: 233 | corrected = False 234 | 235 | close_pair_pos = _current_pairs[pair] 236 | 237 | if len(buffer) < close_pair_pos[0] - 1: 238 | continue 239 | 240 | if len(buffer[close_pair_pos[0] - 1]) <= close_pair_pos[1]: 241 | continue 242 | 243 | if buffer[close_pair_pos[0] - 1][close_pair_pos[1]] != close_pair: 244 | continue 245 | 246 | moved = _delete_at(close_pair_pos, 1) 247 | 248 | try: 249 | with _restore_cursor(): 250 | cursor = _get_cursor() 251 | line = buffer[cursor[0] - 1] 252 | if line[cursor[1] - 1] == close_pair: 253 | _set_cursor(cursor[0], cursor[1] - 1) 254 | 255 | open_pair_pos = vim.Function('searchpairpos')( 256 | _escape_open_pair(open_pair), 257 | "", 258 | close_pair, 259 | "nb", 260 | ) 261 | 262 | open_pair_pos = ( 263 | int(open_pair_pos[0]), 264 | int(open_pair_pos[1]) - 1 265 | ) 266 | 267 | if open_pair_pos <= (0, 0): 268 | continue 269 | 270 | current_pair = (buffer.number, open_pair_pos, close_pair) 271 | 272 | if current_pair not in _current_pairs: 273 | continue 274 | 275 | if _current_pairs[current_pair] == close_pair_pos: 276 | corrected = True 277 | finally: 278 | if not corrected: 279 | _insert_at(close_pair_pos, close_pair) 280 | if moved: 281 | _move_cursor_relative(0, 1) 282 | else: 283 | _insert_at_cursor(close_pair) 284 | del _current_pairs[pair] 285 | return True 286 | 287 | return False 288 | 289 | 290 | def correct_pair(open_pair, close_pair): 291 | if correct_current_pair(open_pair, close_pair): 292 | return True 293 | 294 | if correct_inserted_pair(open_pair, close_pair): 295 | return True 296 | 297 | if skip_matching_pair(open_pair, close_pair): 298 | return False 299 | 300 | _insert_at_cursor(close_pair) 301 | 302 | return True 303 | 304 | 305 | def skip_matching_pair(open_pair, close_pair): 306 | with _restore_cursor(): 307 | cursor = ( 308 | vim.current.window.cursor[0], 309 | vim.current.window.cursor[1] 310 | ) 311 | 312 | next = vim.current.buffer[cursor[0] - 1][cursor[1]:] 313 | 314 | if len(next) == 0: 315 | return False 316 | 317 | if next[0] != close_pair: 318 | return False 319 | 320 | vim.command('normal! %') 321 | 322 | cursor_after = vim.current.window.cursor 323 | 324 | new_cursor = ( 325 | cursor_after[0], 326 | cursor_after[1] 327 | ) 328 | 329 | if new_cursor != cursor: 330 | vim.current.window.cursor = ( 331 | cursor[0], 332 | cursor[1] + 1 333 | ) 334 | 335 | return True 336 | 337 | return False 338 | 339 | 340 | def clean_current_pairs(): 341 | return 342 | global _current_pairs 343 | cursor = vim.current.window.cursor 344 | cursor = (cursor[0], cursor[1] + 1) 345 | 346 | kept_pairs = {} 347 | for open_pair in _current_pairs: 348 | open_pair_pos = open_pair[0] 349 | close_pair_pos = _current_pairs[open_pair] 350 | if open_pair_pos <= cursor <= close_pair_pos or \ 351 | cursor[0] == close_pair_pos[0]: 352 | kept_pairs[open_pair] = close_pair_pos 353 | 354 | _current_pairs = kept_pairs 355 | 356 | 357 | def remove_pair(): 358 | cursor = _get_cursor() 359 | 360 | # current cursor should be at least on 2nd position (start with 0) 361 | # to find a pair 362 | if cursor[1] < 1: 363 | return 364 | 365 | line = vim.current.buffer[cursor[0] - 1] 366 | 367 | match = re.match(RE_OPENING_PAIRS, line[cursor[1]-1]) 368 | if not match: 369 | return 370 | 371 | open_pair = match.group(0)[0] 372 | close_pair = PAIRS[open_pair] 373 | with _restore_cursor(): 374 | _move_cursor_relative(0, -1) 375 | _remove_pair(open_pair, close_pair) 376 | 377 | 378 | def _remove_pair(open_pair, close_pair): 379 | pair_pos = vim.Function('searchpairpos')( 380 | _escape_open_pair(open_pair), 381 | "", 382 | close_pair, 383 | "n", 384 | ) 385 | 386 | if pair_pos[0] == 0: 387 | return 388 | 389 | pair_pos = ( 390 | pair_pos[0], 391 | pair_pos[1] - 1 392 | ) 393 | 394 | _delete_at(pair_pos, 1) 395 | 396 | 397 | def _match_enclosing_quote(cursor): 398 | line = vim.current.buffer[cursor[0] - 1][cursor[1]:] 399 | if len(line) < 1: 400 | return 401 | 402 | match = re.match(r"(['\"`]).*", line) 403 | if not match: 404 | return 405 | 406 | if not _is_cursor_in_string((cursor[0], cursor[1] + 1)): 407 | return 408 | 409 | if cursor[1] > 0: 410 | if _is_cursor_in_string((cursor[0], cursor[1] - 1)): 411 | return 412 | 413 | old_cursor = vim.current.window.cursor 414 | 415 | with _restore_selection_register(): 416 | with _restore_cursor(): 417 | vim.command("normal! \"_va{}\033".format(match.group(1))) 418 | if vim.current.window.cursor > old_cursor: 419 | return ( 420 | vim.current.window.cursor[0], 421 | vim.current.window.cursor[1] + 2 422 | ) 423 | 424 | 425 | def _match_long_identifier(cursor): 426 | line = vim.current.buffer[cursor[0] - 1][cursor[1]:] 427 | match = re.match(r'^([.\w_\[\]-]+)\s', line) 428 | if not match: 429 | return 430 | 431 | if match.group(1).count(']') != match.group(1).count('['): 432 | return 433 | 434 | if _is_cursor_in_string(cursor): 435 | return 436 | 437 | return (cursor[0], cursor[1] + len(match.group(1)) + 1) 438 | 439 | 440 | def _match_enclosing_brace(cursor): 441 | line = vim.current.buffer[cursor[0] - 1][cursor[1]:] 442 | match = re.match(r'^(\[[.\w_\[\]-]+|[.\w_-]*)[([{]', line) 443 | if not match: 444 | return 445 | 446 | if _is_cursor_in_string(cursor): 447 | return 448 | 449 | with _restore_cursor(): 450 | vim.current.window.cursor = cursor 451 | vim.command('normal! {}%'.format( 452 | (str(len(match.group(1))) + 'l') if match.group(1) != '' else '' 453 | )) 454 | 455 | cursor = ( 456 | vim.current.window.cursor[0], 457 | vim.current.window.cursor[1] + 1 458 | ) 459 | 460 | line = vim.current.buffer[cursor[0] - 1][cursor[1]:] 461 | 462 | if re.match(r'^[\w{([]', line): 463 | return _match_enclosing_brace(cursor) 464 | 465 | return (cursor[0], cursor[1]+1) 466 | 467 | 468 | def _match_space(cursor): 469 | if _is_cursor_in_string(cursor): 470 | return 471 | 472 | if len(vim.current.buffer[cursor[0] - 1]) == cursor[1]: 473 | return 474 | 475 | line = vim.current.buffer[cursor[0] - 1][cursor[1]:] 476 | if line.startswith(' ') or line.startswith('\t'): 477 | return (cursor[0], cursor[1]+1) 478 | 479 | return 480 | 481 | def _match_stopper(cursor): 482 | """ 483 | returns current cursor position if any stopper found in the end of line 484 | stopper can be something like = ; , or a space 485 | handles cases like 486 | a[b.c|] = d 487 | press [ 488 | causes plugin return position before ] 489 | """ 490 | if _is_cursor_in_string(cursor): 491 | return 492 | 493 | if len(vim.current.buffer[cursor[0] - 1]) == cursor[1]: 494 | return 495 | 496 | line = vim.current.buffer[cursor[0] - 1][cursor[1]:] 497 | for index in range(len(line)): 498 | if line[index] in '}])]`;=,<>': 499 | return (cursor[0], cursor[1]+1+index) 500 | 501 | return 502 | 503 | 504 | def _match_end_of_code_block(cursor): 505 | if _is_cursor_in_string(cursor): 506 | return 507 | 508 | if len(vim.current.buffer[cursor[0] - 1]) == cursor[1]: 509 | return 510 | 511 | if vim.current.buffer[cursor[0] - 1][-1] in '{[("\'`': 512 | return 513 | 514 | if len(vim.current.buffer) > cursor[0]: 515 | next_line = vim.current.buffer[cursor[0]].strip() 516 | if next_line == '}' or next_line == '': 517 | line = vim.current.buffer[cursor[0] - 1] 518 | matches = re.match(r'^(.*?)[:}]?$', line) 519 | return (cursor[0], len(matches.group(1)) + 1) 520 | 521 | 522 | def _match_end_of_line(cursor): 523 | if _is_cursor_in_string(cursor): 524 | return 525 | 526 | line = vim.current.buffer[cursor[0] - 1] 527 | 528 | if len(line) != cursor[1]: 529 | matches = re.match(r'^[)"\]]+$', line[cursor[1]:]) 530 | if not matches: 531 | return 532 | 533 | return (cursor[0], cursor[1]+1) 534 | 535 | 536 | def _match_argument(cursor): 537 | if _is_cursor_in_string(cursor): 538 | return 539 | 540 | line = vim.current.buffer[cursor[0] - 1][cursor[1]:] 541 | 542 | matches = re.match(r'([\w.]+)[,)\]]', line) 543 | if not matches: 544 | return 545 | 546 | return (cursor[0], cursor[1] + len(matches.group(1)) + 1) 547 | 548 | 549 | def _match_semicolon(cursor): 550 | line = vim.current.buffer[cursor[0] - 1][cursor[1]:] 551 | match = re.match(r'^(.*);', line) 552 | if not match: 553 | return 554 | 555 | if _is_cursor_in_string(cursor): 556 | return 557 | 558 | return (cursor[0], cursor[1] + len(match.group(1)) + 1) 559 | 560 | 561 | def register_finder(callback): 562 | enclosing_strategies.append(callback) 563 | return len(enclosing_strategies) 564 | 565 | 566 | def unregister_finder(index): 567 | enclosing_strategies[index] = None 568 | 569 | 570 | @contextmanager 571 | def _restore_cursor(): 572 | cursor = vim.current.window.cursor 573 | yield 574 | vim.current.window.cursor = cursor 575 | 576 | 577 | @contextmanager 578 | def _restore_selection_register(): 579 | cursor = vim.current.window.cursor 580 | yield 581 | vim.current.window.cursor = cursor 582 | 583 | 584 | def _is_cursor_in_string(cursor): 585 | synstack = vim.Function('synstack')( 586 | cursor[0], 587 | cursor[1] + 1 588 | ) 589 | 590 | for syn_id in synstack: 591 | syn_name = vim.Function('synIDattr')( 592 | vim.Function('synIDtrans')(syn_id), 593 | "name", 594 | ) 595 | if syn_name.lower() == 'string': 596 | return True 597 | break 598 | 599 | return False 600 | 601 | 602 | def _insert_at_cursor(text): 603 | cursor = _get_cursor() 604 | 605 | _insert_at(cursor, text) 606 | 607 | _set_cursor( 608 | cursor[0], 609 | cursor[1] + len(text) 610 | ) 611 | 612 | 613 | def _insert_new_line_at_cursor(count, indentation): 614 | cursor = _get_cursor() 615 | line = cursor[0] - 1 616 | column = cursor[1] 617 | 618 | # adding empty line so lines can be easily moved after new line 619 | for _ in range(count): 620 | vim.current.buffer.append("") 621 | 622 | move_start = line + count 623 | move_end = len(vim.current.buffer) - 1 624 | 625 | text = vim.current.buffer[line][:] 626 | 627 | vim.current.buffer[line] = text[:column] 628 | 629 | target = move_end 630 | while target > move_start: 631 | target_text = vim.current.buffer[target-count] 632 | vim.current.buffer[target] = target_text 633 | target -= 1 634 | 635 | vim.current.buffer[line+count] = indentation + text[column:] 636 | 637 | 638 | def _dump_buffer(): 639 | vimline, vimcolumn = vim.current.window.cursor 640 | 641 | log.warn('--+---------------') 642 | log.warn('current_line: %s', vimline-1) 643 | log.warn('current_column: %s', vimcolumn) 644 | 645 | line_nr = 0 646 | for line in vim.current.buffer: 647 | column = 0 648 | header = [] 649 | text = [] 650 | header.append("%-2s|" % str(line_nr)) 651 | text.append("%-2s|" % str(line_nr)) 652 | for char in line: 653 | header.append("%-2s" % str(column)) 654 | text.append("%-2s" % char) 655 | column += 1 656 | line_nr += 1 657 | 658 | log.warn(' '.join(header)) 659 | log.warn(' '.join(text)) 660 | log.warn('--+---------------') 661 | 662 | def _insert_at(position, text): 663 | line = position[0] - 1 664 | column = position[1] 665 | 666 | vim.current.buffer[line] = \ 667 | vim.current.buffer[line][:column] + \ 668 | text + \ 669 | vim.current.buffer[line][column:] 670 | 671 | 672 | def _delete_at(position, amount): 673 | cursor_before = _get_cursor() 674 | vim.current.buffer[position[0] - 1] = \ 675 | vim.current.buffer[position[0] - 1][:position[1]] + \ 676 | vim.current.buffer[position[0] - 1][position[1] + amount:] 677 | cursor_after = _get_cursor() 678 | 679 | if cursor_before == cursor_after: 680 | if position[0] == cursor_after[0]: 681 | if position[1] < cursor_after[1]: 682 | _move_cursor_relative(0, -amount) 683 | return True 684 | else: 685 | return True 686 | 687 | return False 688 | 689 | 690 | def _move_cursor_relative(line, column): 691 | vim.current.window.cursor = ( 692 | vim.current.window.cursor[0]+line, 693 | vim.current.window.cursor[1]+column 694 | ) 695 | 696 | 697 | def _get_cursor(): 698 | return ( 699 | vim.current.window.cursor[0], 700 | vim.current.window.cursor[1], 701 | ) 702 | 703 | 704 | def _set_cursor(*cursor): 705 | vim.current.window.cursor = cursor 706 | 707 | 708 | def _escape_open_pair(pair): 709 | # need escape [ for searchpairpos since it'll be treated as regexp symbol 710 | if pair == "[": 711 | return "\\[" 712 | return pair 713 | 714 | 715 | enclosing_strategies = [] 716 | register_finder(_match_space) 717 | register_finder(_match_long_identifier) 718 | register_finder(_match_enclosing_brace) 719 | register_finder(_match_argument) 720 | register_finder(_match_stopper) 721 | register_finder(_match_semicolon) 722 | register_finder(_match_end_of_code_block) 723 | register_finder(_match_enclosing_quote) 724 | register_finder(_match_end_of_line) 725 | --------------------------------------------------------------------------------