├── VimFlawor ├── .gitignore ├── .travis.yml ├── test ├── fixture3.txt ├── fixture.txt ├── helpers.vim ├── fixture2.txt ├── normal_mode_test_virtual_columns.vim ├── visual_mode_test.vim ├── miscellaneous_test.vim ├── visual_block_mode_test.vim ├── normal_mode_test.vim └── operator_pending_motions_test.vim ├── Gemfile ├── examples ├── vertical_move_block_operation.gif └── block_select_original_paragraph_motion.gif ├── Rakefile ├── Gemfile.lock ├── LICENSE ├── plugin └── vertical_move.vim ├── README.md ├── autoload └── vertical_move.vim └── doc └── vertical_move.txt /VimFlawor: -------------------------------------------------------------------------------- 1 | # No dependencies. 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vim-flavor 2 | VimFlavor.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.9.3 4 | script: rake ci 5 | -------------------------------------------------------------------------------- /test/fixture3.txt: -------------------------------------------------------------------------------- 1 | linex1 2 | line 2 3 | line 3y 4 | 5 | " vim:set ts=2: 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'vim-flavor', '~> 1.1' 4 | gem 'rake' 5 | -------------------------------------------------------------------------------- /test/fixture.txt: -------------------------------------------------------------------------------- 1 | line 1 2 | line 2 3 | line 3 4 | 5 | line 5 6 | line 6 7 | l 7 8 | line 8 9 | 10 | line 10 11 | l 11 12 | -------------------------------------------------------------------------------- /examples/vertical_move_block_operation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vim-utils/vim-vertical-move/HEAD/examples/vertical_move_block_operation.gif -------------------------------------------------------------------------------- /examples/block_select_original_paragraph_motion.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vim-utils/vim-vertical-move/HEAD/examples/block_select_original_paragraph_motion.gif -------------------------------------------------------------------------------- /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 ./test' 11 | end 12 | -------------------------------------------------------------------------------- /test/helpers.vim: -------------------------------------------------------------------------------- 1 | function! CharacterUnderCursor() 2 | return getline('.')[col('.')-1] 3 | endfunction 4 | 5 | function! PositionToString(string) 6 | execute "normal /".a:string."\" 7 | return 8 | endfunction 9 | -------------------------------------------------------------------------------- /test/fixture2.txt: -------------------------------------------------------------------------------- 1 | class Foo: 2 | def on_request(self, message): 3 | pass 4 | def respond(self): 5 | pass 6 | 7 | class Bar(Foo): 8 | def on_request(self, message): 9 | def respond(self): 10 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | blankslate (2.1.2.4) 5 | parslet (1.5.0) 6 | blankslate (~> 2.0) 7 | rake (10.1.0) 8 | thor (0.18.1) 9 | vim-flavor (1.1.3) 10 | parslet (~> 1.0) 11 | thor (~> 0.14) 12 | 13 | PLATFORMS 14 | ruby 15 | 16 | DEPENDENCIES 17 | rake 18 | vim-flavor (~> 1.1) 19 | -------------------------------------------------------------------------------- /test/normal_mode_test_virtual_columns.vim: -------------------------------------------------------------------------------- 1 | source test/helpers.vim 2 | source plugin/vertical_move.vim 3 | 4 | describe 'vertical-move normal mode with virtual columns' 5 | 6 | before 7 | new 8 | read test/fixture3.txt 9 | set tabstop=2 10 | end 11 | 12 | after 13 | close! 14 | end 15 | 16 | it ']v takes displayed tab character width into account' 17 | normal gg$h 18 | Expect CharacterUnderCursor() == 'x' 19 | normal ]v 20 | Expect CharacterUnderCursor() == 'y' 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /test/visual_mode_test.vim: -------------------------------------------------------------------------------- 1 | source test/helpers.vim 2 | source plugin/vertical_move.vim 3 | 4 | describe 'vertical-move visual mode' 5 | 6 | before 7 | new 8 | read test/fixture.txt 9 | end 10 | 11 | after 12 | close! 13 | end 14 | 15 | it ']v works with deletion' 16 | normal ggg_ 17 | exe "normal v]vd" 18 | Expect getline(1) == 'line ' 19 | Expect getline(2) == '' 20 | Expect getline(3) == 'line 5' 21 | end 22 | 23 | it ']v skips gaps' 24 | normal ggg_ 25 | exe "normal v]v]vd" 26 | Expect getline(1) == 'line ' 27 | Expect getline(2) == 'l 7' 28 | end 29 | 30 | it '[v works with deletion' 31 | call PositionToString('3') 32 | exe "normal v[vd" 33 | Expect getline(1) == 'line ' 34 | Expect getline(2) == '' 35 | Expect getline(3) == 'line 5' 36 | end 37 | 38 | it '[v skips gaps' 39 | call PositionToString('6') 40 | exe "normal v[v[vd" 41 | Expect getline(1) == 'line ' 42 | Expect getline(2) == 'l 7' 43 | end 44 | 45 | end 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | vim-vertical-move plugin is released under MIT license 2 | 3 | Copyright (C) 2013 Bruno Sutic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included 13 | in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 20 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 21 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /plugin/vertical_move.vim: -------------------------------------------------------------------------------- 1 | if exists('g:loaded_vertical_move') && g:loaded_vertical_move 2 | finish 3 | endif 4 | let g:loaded_vertical_move = 1 5 | 6 | let s:save_cpo = &cpo 7 | set cpo&vim 8 | 9 | nnoremap (vertical_move_down) :call vertical_move#Down('', v:count1) 10 | nnoremap (vertical_move_up) :call vertical_move#Up('', v:count1) 11 | xnoremap (vertical_move_down) :call vertical_move#Down('v', v:count1) 12 | xnoremap (vertical_move_up) :call vertical_move#Up('v', v:count1) 13 | 14 | " Operator pending mappings 15 | onoremap (vertical_move_down) :call vertical_move#PendingDown(v:count1) 16 | onoremap (vertical_move_up) :call vertical_move#PendingUp(v:count1) 17 | 18 | if get(g:, 'vertical_move_default_mapping', 1) 19 | nmap ]v (vertical_move_down) 20 | nmap [v (vertical_move_up) 21 | xmap ]v (vertical_move_down) 22 | xmap [v (vertical_move_up) 23 | omap ]v (vertical_move_down) 24 | omap [v (vertical_move_up) 25 | endif 26 | 27 | let &cpo = s:save_cpo 28 | unlet s:save_cpo 29 | -------------------------------------------------------------------------------- /test/miscellaneous_test.vim: -------------------------------------------------------------------------------- 1 | source test/helpers.vim 2 | source plugin/vertical_move.vim 3 | 4 | describe 'vertical-move miscellaneous' 5 | 6 | before 7 | new 8 | read test/fixture2.txt 9 | end 10 | 11 | after 12 | close! 13 | end 14 | 15 | it ']v does not change the current search string' 16 | normal gg_ 17 | " do a search and check cursor position 18 | exe "normal /Foo\" 19 | Expect line('.') == 1 20 | Expect col('.') == 7 21 | " do vim-vertical-move motion 22 | exe "normal ]v" 23 | Expect line('.') == 5 24 | Expect col('.') == 7 25 | " performing the same search as before hitting ]v 26 | exe "normal /\" 27 | Expect line('.') == 7 28 | Expect col('.') == 11 29 | end 30 | 31 | it ']v does not change the current search string' 32 | normal gg_ 33 | " do a search and check cursor position 34 | exe "normal /Bar\" 35 | Expect line('.') == 7 36 | Expect col('.') == 7 37 | " make vim-vertical-move motion 38 | exe "normal [v" 39 | Expect line('.') == 1 40 | Expect col('.') == 7 41 | " performing the same search as before hitting [v 42 | exe "normal /\" 43 | Expect line('.') == 7 44 | Expect col('.') == 7 45 | end 46 | 47 | end 48 | -------------------------------------------------------------------------------- /test/visual_block_mode_test.vim: -------------------------------------------------------------------------------- 1 | source test/helpers.vim 2 | source plugin/vertical_move.vim 3 | 4 | describe 'vertical-move visual block mode' 5 | 6 | before 7 | new 8 | read test/fixture.txt 9 | end 10 | 11 | after 12 | close! 13 | end 14 | 15 | it ']v works with deletion' 16 | normal gg$ 17 | exe "normal \]vrx" 18 | Expect getline(1) == 'line x' 19 | Expect getline(2) == 'line x' 20 | Expect getline(3) == 'line x' 21 | end 22 | 23 | it ']v skips gaps' 24 | normal gg$ 25 | exe "normal \]v]vrx" 26 | Expect getline(1) == 'line x' 27 | Expect getline(2) == 'line x' 28 | Expect getline(3) == 'line x' 29 | Expect getline(4) == '' 30 | Expect getline(5) == 'line x' 31 | Expect getline(6) == 'line x' 32 | end 33 | 34 | it '[v works with deletion' 35 | call PositionToString('3') 36 | exe "normal \[vrx" 37 | Expect getline(1) == 'line x' 38 | Expect getline(2) == 'line x' 39 | Expect getline(3) == 'line x' 40 | end 41 | 42 | it '[v skips gaps' 43 | call PositionToString('6') 44 | exe "normal \[v[vrx" 45 | Expect getline(1) == 'line x' 46 | Expect getline(2) == 'line x' 47 | Expect getline(3) == 'line x' 48 | Expect getline(4) == '' 49 | Expect getline(5) == 'line x' 50 | Expect getline(6) == 'line x' 51 | end 52 | 53 | end 54 | -------------------------------------------------------------------------------- /test/normal_mode_test.vim: -------------------------------------------------------------------------------- 1 | source test/helpers.vim 2 | source plugin/vertical_move.vim 3 | 4 | describe 'vertical-move normal mode' 5 | 6 | before 7 | new 8 | read test/fixture.txt 9 | end 10 | 11 | after 12 | close! 13 | end 14 | 15 | it ']v jumps to the last line in current paragraph' 16 | normal gg$ 17 | normal ]v 18 | Expect CharacterUnderCursor() == '3' 19 | end 20 | 21 | it ']v jumps over empty lines when invoked immediately near one' 22 | call PositionToString('3') 23 | normal ]v 24 | Expect CharacterUnderCursor() == '6' 25 | 26 | normal ]v 27 | Expect CharacterUnderCursor() == '8' 28 | end 29 | 30 | it ']v takes a count' 31 | normal ggg_ 32 | normal 1]v 33 | Expect CharacterUnderCursor() == '3' 34 | 35 | normal ggg_ 36 | normal 2]v 37 | Expect CharacterUnderCursor() == '6' 38 | 39 | normal ggg_ 40 | normal 3]v 41 | Expect CharacterUnderCursor() == '8' 42 | end 43 | 44 | it '[v jumps to the first line in current paragraph' 45 | call PositionToString('6') 46 | normal [v 47 | Expect CharacterUnderCursor() == '5' 48 | end 49 | 50 | it '[v jumps over empty lines when invoked immediately near one' 51 | call PositionToString('8') 52 | normal [v 53 | Expect CharacterUnderCursor() == '5' 54 | normal [v 55 | Expect CharacterUnderCursor() == '1' 56 | end 57 | 58 | it '[v takes a count' 59 | call PositionToString('10') 60 | normal 1[v 61 | Expect CharacterUnderCursor() == '8' 62 | 63 | call PositionToString('10') 64 | normal 2[v 65 | Expect CharacterUnderCursor() == '5' 66 | 67 | call PositionToString('10') 68 | normal 3[v 69 | Expect CharacterUnderCursor() == '1' 70 | end 71 | 72 | end 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-vertical-move 2 | 3 | ![Build Status](https://travis-ci.org/bruno-/vim-vertical-move.png?branch=master) 4 | 5 | Adds vertical move motions to vim. These move a cursor 'up' or 'down' as many 6 | lines as possible **without changing the cursor column**. With these motions - 7 | cursor **always** stays in the same column! These are especially useful in 8 | visual-block mode. 9 | 10 | These motions are a bit hard to verbally explain, so be sure to check the 11 | animated examples below. 12 | 13 | ### Motions 14 | 15 | * `[v` - moves 'up' as many lines as possible without changing the 16 | cursor column 17 | * `]v` - moves 'down' as many lines as possible. Also doesn't change 18 | the cursor column 19 | 20 | ### Examples 21 | 22 | * Problem scenario: selecting a block of text with vertical movements with 23 | vanilla vim (vim-vertical-move not installed). Notice how the **cursor column 24 | position is lost** when using the original paragraph motion `}`.
25 | ![original paragraph motion example](/examples/block_select_original_paragraph_motion.gif) 26 | 27 | * Solution using vim-vertical-move: pressing `]v` the cursor moves 28 | 'down' as many lines as possible, without changing the cursor column.
29 | ![vertical move motion example](/examples/vertical_move_block_operation.gif) 30 | 31 | ### Installation 32 | 33 | * Pathogen
34 | `git clone git://github.com/bruno-/vim-vertical-move.git ~/.vim/bundle/vim-vertical-move` 35 | 36 | * Vundle
37 | `Plugin 'bruno-/vim-vertical-move` 38 | 39 | * vim-plug
40 | `Plug 'bruno-/vim-vertical-move` 41 | 42 | ### Contributing and development 43 | 44 | Patches are contributions are welcome. 45 | 46 | This plugin has a decent [vspec](https://github.com/kana/vim-vspec) test 47 | coverage. If your patch is non-trivial, please write tests for it. 48 | 49 | For instructions how to run and write tests see `:h vertical-move-contributing`. 50 | 51 | ### License 52 | 53 | [MIT](LICENSE) 54 | -------------------------------------------------------------------------------- /autoload/vertical_move.vim: -------------------------------------------------------------------------------- 1 | if exists('g:autoloaded_vertical_move') && g:autoloaded_vertical_move 2 | finish 3 | endif 4 | let g:autoloaded_vertical_move = 1 5 | 6 | function! s:line_length(line) 7 | return strdisplaywidth(getline(a:line)) 8 | endfunction 9 | 10 | function! s:above_line_length() 11 | return s:line_length(line('.')-1) 12 | endfunction 13 | 14 | function! s:below_line_length() 15 | return s:line_length(line('.')+1) 16 | endfunction 17 | 18 | function! s:up_bridge_gap(cursor_column) 19 | let next_line = line('.')-1 20 | let move_count = 1 21 | 22 | while next_line > 1 && s:line_length(next_line) < a:cursor_column 23 | let next_line -= 1 24 | let move_count += 1 25 | endwhile 26 | 27 | if move_count > 1 && s:line_length(next_line) >= a:cursor_column 28 | execute 'norm! '.move_count.'k' 29 | endif 30 | endfunction 31 | 32 | function! s:down_bridge_gap(cursor_column) 33 | let total_lines = line('$') 34 | let next_line = line('.')+1 35 | let move_count = 1 36 | 37 | while next_line < total_lines && s:line_length(next_line) < a:cursor_column 38 | let next_line += 1 39 | let move_count += 1 40 | endwhile 41 | 42 | if move_count > 1 && s:line_length(next_line) >= a:cursor_column 43 | execute 'norm! '.move_count.'j' 44 | endif 45 | endfunction 46 | 47 | " operator pending functions 48 | function! vertical_move#PendingDown(count) 49 | " norm! 'resets' the visual mode 50 | norm! v 51 | call vertical_move#Down('v', a:count) 52 | endfunction 53 | 54 | function! vertical_move#PendingUp(count) 55 | " norm! 'resets' the visual mode 56 | norm! v 57 | call vertical_move#Up('v', a:count) 58 | endfunction 59 | 60 | " 'public' functions below 61 | function! vertical_move#Down(mode, count) 62 | norm! m' 63 | if a:mode ==# 'v' 64 | norm! gv 65 | endif 66 | 67 | let i = 0 68 | while i < a:count 69 | let i += 1 70 | let cursor_column = virtcol('.') 71 | let total_lines = line('$') 72 | 73 | " first line below is shorter than cursor column, 'bridge' the gap 74 | if s:below_line_length() < cursor_column 75 | call s:down_bridge_gap(cursor_column) 76 | endif 77 | 78 | " move to the bottom of the current paragraph 79 | while line('.') < total_lines && s:below_line_length() >= cursor_column 80 | execute 'norm! j' 81 | endwhile 82 | endwhile 83 | endfunction 84 | 85 | function! vertical_move#Up(mode, count) 86 | norm! m' 87 | if a:mode ==# 'v' 88 | norm! gv 89 | endif 90 | 91 | 92 | let i = 0 93 | while i < a:count 94 | let i += 1 95 | let cursor_column = virtcol('.') 96 | 97 | " first line above is shorter than cursor column, 'bridge' the gap 98 | if s:above_line_length() < cursor_column 99 | call s:up_bridge_gap(cursor_column) 100 | endif 101 | 102 | " move to the top of the current paragraph 103 | while line('.') > 1 && s:above_line_length() >= cursor_column 104 | execute 'norm! k' 105 | endwhile 106 | endwhile 107 | endfunction 108 | -------------------------------------------------------------------------------- /test/operator_pending_motions_test.vim: -------------------------------------------------------------------------------- 1 | source test/helpers.vim 2 | source plugin/vertical_move.vim 3 | 4 | describe 'vertical-move operator pending mappings' 5 | 6 | before 7 | new 8 | read test/fixture.txt 9 | end 10 | 11 | after 12 | close! 13 | end 14 | 15 | " describe 'single movement' 16 | it 'd]v deletes the same text that would be selected with v]v' 17 | normal gg0 18 | normal d]v 19 | Expect getline(1) == 'ine 3' 20 | end 21 | 22 | it 'c]v deletes the same text that would be selected with v]v and enters insert mode' 23 | normal gg0 24 | normal c]vabc 25 | Expect getline(1) == 'abcine 3' 26 | end 27 | 28 | it 'y]v yanks the same text that would be selected with v]v' 29 | normal gg0 30 | normal y]v 31 | Expect getreg('"') == "line 1\nline 2\nl" 32 | " when yanking, cursor doesn't move 33 | Expect line('.') == 1 34 | Expect col('.') == 1 35 | end 36 | 37 | " [v" 38 | it 'd[v deletes the same text that would be selected with v[v' 39 | call PositionToString('6') 40 | normal d[v 41 | Expect getline(5) == 'line ' 42 | Expect getline(6) == 'l 7' 43 | end 44 | 45 | it 'c[v deletes the same text that would be selected with v[v and enters insert mode' 46 | call PositionToString('6') 47 | normal c[vabc 48 | Expect getline(5) == 'line abc' 49 | end 50 | 51 | it 'y[v yanks the same text that would be selected with v[v' 52 | call PositionToString('6') 53 | normal y[v 54 | Expect getreg('"') == "5\nline 6" 55 | " when yanking up, cursor moves, analog to `y{` 56 | Expect line('.') == 5 57 | Expect col('.') == 6 58 | end 59 | 60 | " describe 'multiple movements' 61 | it 'd2]v deletes the same text that would be selected with v2]v' 62 | normal gg0 63 | normal d2]v 64 | Expect getline(1) == 'ine 8' 65 | end 66 | 67 | it 'c3]v deletes the same text that would be selected with v3]v and enters insert mode' 68 | normal gg0 69 | normal c3]vabc 70 | Expect getline(1) == 'abc 11' 71 | end 72 | 73 | it 'y2]v yanks the same text that would be selected with v2]v' 74 | normal gg0 75 | normal y2]v 76 | Expect getreg('"') == "line 1\nline 2\nline 3\n\nline 5\nline 6\nl 7\nl" 77 | " when yanking, cursor doesn't move 78 | Expect line('.') == 1 79 | Expect col('.') == 1 80 | end 81 | 82 | " [v" 83 | it 'd2[v deletes the same text that would be selected with v2[v' 84 | call PositionToString('e 8') 85 | normal d2[v 86 | Expect getline(1) == 'lin 8' 87 | end 88 | 89 | it 'c3[v deletes the same text that would be selected with v3[v and enters insert mode' 90 | call PositionToString('11') 91 | normal c3[vabc 92 | Expect getline(1) == 'liabc1' 93 | end 94 | 95 | it 'y2[v yanks the same text that would be selected with v2[v' 96 | call PositionToString('e 8') 97 | normal y2[v 98 | Expect getreg('"') == "e 1\nline 2\nline 3\n\nline 5\nline 6\nl 7\nline" 99 | " when yanking upward, cursor moves (analog to y2{) 100 | Expect line('.') == 1 101 | Expect col('.') == 4 102 | end 103 | 104 | end 105 | -------------------------------------------------------------------------------- /doc/vertical_move.txt: -------------------------------------------------------------------------------- 1 | *vertical_move.txt* Motions that move a cursor 'up' or 'down' as many 2 | lines as possible without changing the cursor column. 3 | With these motions - cursor always stays in the same 4 | column. 5 | 6 | Here's the obligatory ascii art: 7 | _ _ _ ~ 8 | __ _____ _ __| |_(_) ___ __ _| | _ __ ___ _____ _____ ~ 9 | \ \ / / _ \ '__| __| |/ __/ _` | | | '_ ` _ \ / _ \ \ / / _ \ ~ 10 | \ V / __/ | | |_| | (_| (_| | | | | | | | | (_) \ V / __/ ~ 11 | \_/ \___|_| \__|_|\___\__,_|_| |_| |_| |_|\___/ \_/ \___| ~ 12 | 13 | 14 | Author: Bruno Sutic 15 | 16 | |vertical-move-introduction| Short introduction 17 | |vertical-move-usage| Useful usage example 18 | |vertical-move-mappings| Provided mappings 19 | |vertical-move-examples| Detailed examples 20 | |vertical-move-about| Why the plugin is created 21 | |vertical-move-contributing| Development setup instructions 22 | |vertical-move-changelog| Changelog 23 | |vertical-move-license| MIT 24 | 25 | 26 | INTRODUCTION *vertical-move* *vertical-move-introduction* 27 | 28 | This plugin adds vertical move motions to vim. They move the cursor 'up' or 29 | 'down' as many lines as possible without changing the cursor column position. 30 | With these motions - cursor always stays in the same column! 31 | This is especially useful when manipulating text in visual-block mode. 32 | 33 | 34 | USAGE *vertical-move-usage* 35 | 36 | Example presented here is also available as an animated gif on the project 37 | repository page 38 | https://github.com/bruno-/vim-vertical-move 39 | 40 | Vertical move motions are really useful when manipulating text in visual block 41 | mode. 42 | A good example that shows this is presented below. Let's say you want to change 43 | the background image url for classes `img_01` to `img_05`. 44 | The cursor is represented as a pipe character `|`, currently in the first line. 45 | > 46 | .img_01 { background-image: image-url('|service-1-sprite.png'); } 47 | .img_02 { background-image: image-url('service-1-sprite.png'); } 48 | .img_03 { background-image: image-url('service-1-sprite.png'); } 49 | .img_04 { background-image: image-url('service-1-sprite.png'); } 50 | .img_05 { background-image: image-url('service-1-sprite.png'); } 51 | 52 | .img_06 { background-image: image-url('service-2-sprite.png'); } 53 | .img_07 { background-image: image-url('service-2-sprite.png'); } 54 | < 55 | 56 | To accomplish the task, we want to visually select all 5 instances of text 57 | 'service-1-sprite.png' with visual block mode and change it. 58 | 59 | Enter visual block mode with || and press `]v`. 60 | Cursor will stay in the same text column and go down 4 lines, still in visual 61 | block mode. 62 | This is similar but different from the `}` motion behavior, where the cursor 63 | would end up on the empty line below (in the first column), which is not 64 | desirable in this context. 65 | > 66 | .img_01 { background-image: image-url('service-1-sprite.png'); } 67 | .img_02 { background-image: image-url('service-1-sprite.png'); } 68 | .img_03 { background-image: image-url('service-1-sprite.png'); } 69 | .img_04 { background-image: image-url('service-1-sprite.png'); } 70 | .img_05 { background-image: image-url('|service-1-sprite.png'); } 71 | 72 | .img_06 { background-image: image-url('service-2-sprite.png'); } 73 | .img_07 { background-image: image-url('service-2-sprite.png'); } 74 | < 75 | 76 | The last command that will visually select text we want to update is `f'` which 77 | places a cursor at the end of 'service-1-sprite.png' string in line 5. 78 | Now it's easy to change the selected text, for example with `c` command (but we 79 | won't go further than that, since the point is illustrated). 80 | > 81 | .img_01 { background-image: image-url('service-1-sprite.png'); } 82 | .img_02 { background-image: image-url('service-1-sprite.png'); } 83 | .img_03 { background-image: image-url('service-1-sprite.png'); } 84 | .img_04 { background-image: image-url('service-1-sprite.png'); } 85 | .img_05 { background-image: image-url('service-1-sprite.png|'); } 86 | 87 | .img_06 { background-image: image-url('service-2-sprite.png'); } 88 | .img_07 { background-image: image-url('service-2-sprite.png'); } 89 | < 90 | 91 | MAPPINGS *vertical-move-mappings* 92 | 93 | Just 2 mappings are provided by default: 94 | *vertical-move-up* 95 | [count] [v Moves 'up' as many lines as possible without 96 | changing the cursor column. 97 | 98 | *vertical-move-down* 99 | [count] ]v Moves 'down' as many lines as possible. Also 100 | doesn't change the cursor column. 101 | 102 | *g:vertical_move_default_mapping* 103 | You can remap these mappins to your liking. Here's the example how to remap 104 | the vertical move motions to `j` and `k` in your .vimrc: 105 | > 106 | let g:vertical_move_default_mapping = 0 107 | nmap j (vertical_move_down) 108 | nmap k (vertical_move_up) 109 | xmap j (vertical_move_down) 110 | xmap k (vertical_move_up) 111 | < 112 | 113 | EXAMPLES *vertical-move-examples* 114 | 115 | This plugin is little bit hard to verbally explain so here are the step-by-step 116 | examples of how it works. 117 | 118 | They say a picture is worth a thousand words. Video then should be worth even 119 | more. There are animated gifs that show the behavior of this plugin on the 120 | project's github repository: 121 | https://github.com/bruno-/vim-vertical-move 122 | 123 | Example 1: ~ 124 | 125 | In the below example cursor is near the end of first line (marked with `|`). 126 | Lines contain a number at the beginning so they are easier referenced. Line 4 127 | is an empty line. 128 | > 129 | 1. Lorem ipsum dolor sit amet|, 130 | 2. consectetur adipisicing elit, 131 | 3. sed do eiusmod tempor incididunt 132 | 133 | 5. ut labore et dolore magna aliqua. 134 | 6. Ut enim ad minim veniam. 135 | < 136 | 137 | When `]v` is pressed, cursor will go down as many lines as it can, without 138 | changing the cursor column. The new cursor position is in the last word of 139 | line 3 (see the `|` sign?). That position is exactly 2 characters below the 140 | position the cursor had in line 1. It is equivalent to pressing 'jj' two times 141 | in normal mode. 142 | Cursor won't go to the line 4, because it would automatically be positioned in 143 | the column 1 there. That would violate the requirement of this plugin: 144 | "cursor always stays in the same column". 145 | > 146 | 1. Lorem ipsum dolor sit amet, 147 | 2. consectetur adipisicing elit, 148 | 3. sed do eiusmod tempor inci|didunt 149 | 150 | 5. ut labore et dolore magna aliqua. 151 | 6. Ut enim ad minim veniam. 152 | < 153 | 154 | Notice this is similar to `}` motion. With `}`, cursor would end up on line 4 155 | (outside the paragraph), whereas with vertical-move, cursor always stays 156 | within it. 157 | 158 | If `]v` is pressed again, cursor will jump over the gap (empty line) and 159 | continue on line 5. It will stop just before the last word in line 5 (cursor 160 | indicated by `|` character) in the same text column as it was in the line 3 161 | and in line 1 before that. 162 | Cursor won't go to line 6, because by doing that it would automatically be 163 | placed in a different text column (since line 6 is shorter than the current 164 | cursor position in line 5). 165 | > 166 | 1. Lorem ipsum dolor sit amet, 167 | 2. consectetur adipisicing elit, 168 | 3. sed do eiusmod tempor incididunt 169 | 170 | 5. ut labore et dolore magna |aliqua. 171 | 6. Ut enim ad minim veniam. 172 | < 173 | 174 | By pressing the `[v`, cursor will now jump over the gap in the upward 175 | direction and go 'up' as much as possible without changing the text colum. You 176 | might have guessed it, it will end up in in the same spot as it was before, at 177 | the end of line 1. 178 | > 179 | 1. Lorem ipsum dolor sit amet|, 180 | 2. consectetur adipisicing elit, 181 | 3. sed do eiusmod tempor incididunt 182 | 183 | 5. ut labore et dolore magna aliqua. 184 | 6. Ut enim ad minim veniam. 185 | < 186 | 187 | Example 2: ~ 188 | 189 | This example clarifies the behavior of this plugin when there's no empty line. 190 | As in the previous example, we start on line 1. Cursor is indicated by the `|` 191 | character. 192 | > 193 | 1. Lorem ipsum dolor sit amet|, 194 | 2. consectetur adipisicing elit, 195 | 3. sed do 196 | 4. eiusmod tempor incididunt ut labore 197 | 5. et dolore magna aliqua. 198 | < 199 | 200 | When `]v` is pressed, cursor goes down just one line. It won't go to line 3 201 | because by doing that, it would automatically change text column. 202 | This example strives to emphasize that any line with fewer characters than 203 | current cursor position number is considered 'a gap'. 204 | > 205 | 1. Lorem ipsum dolor sit amet, 206 | 2. consectetur adipisicing el|it, 207 | 3. sed do 208 | 4. eiusmod tempor incididunt ut labore 209 | 5. et dolore magna aliqua. 210 | < 211 | 212 | If `]v` is pressed again, since cursor is just next to 'the gap', it will jump 213 | over it and end up in line 4. 214 | If `]v` is pressed again, cursor won't move. This example file does not have 215 | line 6. If it moved down to line 5, it would change current text column, so 216 | that won't happen. 217 | > 218 | 1. Lorem ipsum dolor sit amet, 219 | 2. consectetur adipisicing elit, 220 | 3. sed do 221 | 4. eiusmod tempor incididunt |ut labore 222 | 5. et dolore magna aliqua. 223 | < 224 | 225 | ABOUT *vertical-move-about* 226 | 227 | As mentioned in the examples and usage sections, this plugin is written from 228 | the desire to better handle text manipulation, especially in |visual-block| 229 | mode. 230 | The 'need' for this motion came repeatedly in author's day-to-day work. After 231 | learning enough vim script, this plugin was born. 232 | 233 | 234 | CONTRIBUTING *vertical-move-contributing* 235 | 236 | Plugin repository is on github: 237 | https://github.com/bruno-/vim-vertical-move 238 | 239 | Development environment ~ 240 | 241 | This plugin has a test suite written in 'vspec'. 'vspec' itself depends 242 | on a ruby gem 'vim-flavor'. Here are the steps to run the specs locally. 243 | 244 | Clone the git repo 245 | git clone https://github.com/bruno-/vim-vertical-move 246 | cd vim-vertical-move 247 | 248 | Install the gems specified in Gemfile. This requires the bundler gem to be 249 | installed. 250 | bundle install 251 | 252 | Run the specs with 253 | bundle exec rake test 254 | 255 | You should see the test suite output with a message 'All tests successful' 256 | 257 | For more informaion about 'vspec' plugin, link to vimcast tutorial and other 258 | visit 259 | https://github.com/kana/vim-vspec 260 | 261 | Contributing ~ 262 | 263 | Code contributions and patches are welcome. If the patch contains a 264 | non-trivial update, please also write tests for it. 265 | 266 | Contributors ~ 267 | 268 | A list of code contributors: 269 | itchyny (https://github.com/itchyny) 270 | 271 | 272 | CHANGELOG *vertical-move-changelog* 273 | 274 | v2.1.0: February 13, 2015 * Improve string length calculation, 275 | various improvements 276 | v2.0.0: February 2, 2014 * Change default mappings to ]v and [v 277 | because 'leader mappings are lame' 278 | (http://lanyrd.com/2014/vimlondon/scwggw/) 279 | v1.0.1: December 21, 2013 * Always use 'norm!' - fixes bug with 280 | wrapped lines 281 | v1.0.0: December 5, 2013 * @itchyny enables configurable 282 | mappings. 283 | v0.0.4: November 30, 2013 * Change name to vim-vertical-move 284 | v0.0.3: November 24, 2013 * Add operator pending mappings and 285 | specs for them 286 | v0.0.2: November 24, 2013 * Added documentation 287 | v0.0.1: November 23, 2013 * Initial working version. Added 288 | animated gifs that show plugin 289 | behavior 290 | 291 | 292 | LICENSE *vertical-move-license* 293 | 294 | Copyright (c) Bruno Sutic. Distributed under the MIT license. 295 | 296 | vim:tw=78:ts=8:ft=help:norl: 297 | --------------------------------------------------------------------------------