diff --git a/plugin/move.vim b/plugin/move.vim index dceda3d..a8d0467 100644 --- a/plugin/move.vim +++ b/plugin/move.vim @@ -26,192 +26,157 @@ if !exists('g:move_past_end_of_line') let g:move_past_end_of_line = 1 endif -function! s:SaveDefaultRegister() - let s:default_register_value = @" -endfunction - -function! s:RestoreDefaultRegister() - let @" = s:default_register_value -endfunction - -function s:MoveBlockVertically(distance) range +" +" In normal mode, move the current line vertically. +" Moves down if (distance > 0) and up if (distance < 0). +" +function! s:MoveLineVertically(distance) if !&modifiable return endif + " Remember the current cursor position. When we move or reindent a line + " Vim will move the cursor to the first non-blank character. + let l:old_cursor_col = virtcol('.') + normal! ^ + let l:old_indent = virtcol('.') + if a:distance <= 0 - let l:after = max([1, a:firstline + a:distance]) - 1 + let l:after = max([1, line('.') + a:distance]) - 1 else - let l:after = min([line('$'), a:lastline + a:distance]) + let l:after = min([line('$'), line('.') + a:distance]) endif - execute 'silent' a:firstline ',' a:lastline 'move ' l:after + execute 'move' l:after if g:move_auto_indent - normal! gv= + normal! == endif - normal! gv + " Restore the cursor column, taking indentation changes into account. + let l:new_indent = virtcol('.') + let l:new_cursor_col = max([1, l:old_cursor_col - l:old_indent + l:new_indent]) + execute 'normal!' (l:new_cursor_col . '|') endfunction -function! s:MoveBlockLeft(distance) range - let l:min_col = min([virtcol("'<"), virtcol("'>")]) - let l:distance = min([a:distance, l:min_col - 1]) - - if !&modifiable || virtcol("$") == 1 || l:distance <= 0 || visualmode() ==# "V" - normal! gv - return - endif - - if visualmode() ==# "v" && a:lastline - a:firstline > 0 - execute "silent normal! gv\" - echomsg "Switching to visual block mode for moving multiple lines with MoveBlockLeft" +" +" In visual mode, move the selected lines vertically. +" Moves down if (distance > 0) and up if (distance < 0). +" +function s:MoveBlockVertically(distance) + if !&modifiable return endif - let [l:old_virtualedit, &virtualedit] = [&virtualedit, 'onemore'] - call s:SaveDefaultRegister() + let l:first = line("'<") + let l:last = line("'>") - " save previous cursor position - silent normal! gv - let l:row_pos = getcurpos()[1] - let l:is_rhs = virtcol(".") == max([virtcol("'<"), virtcol("'>")]) - - execute 'silent normal! gvd' . l:distance . "hP`[\`]" + if a:distance <= 0 + let l:after = max([1, l:first + a:distance]) - 1 + else + let l:after = min([line('$'), l:last + a:distance]) + endif + execute l:first ',' l:last 'move ' l:after - " restore previous cursor position - if getcurpos()[1] != l:row_pos - silent normal! o - if l:is_rhs - silent normal! O - endif - elseif !l:is_rhs - silent normal! O + if g:move_auto_indent + normal! gv= endif - call s:RestoreDefaultRegister() - let &virtualedit = l:old_virtualedit + normal! gv endfunction -function! s:MoveBlockRight(distance) range - let l:max_col = max([virtcol("'<"), virtcol("'>")]) - - let l:distance = a:distance - if !g:move_past_end_of_line - let l:shorter_line_len = min(map(getline(a:firstline, a:lastline), 'strwidth(v:val)')) - let l:distance = min([l:shorter_line_len - l:max_col, l:distance]) - end - - if !&modifiable || virtcol("$") == 1 || l:distance <= 0 - normal! gv +" +" In normal mode, move the character under the cursor horizontally +" Moves right (distance > 0) and left if (distance < 0). +" +function! s:MoveCharHorizontally(distance) + if !&modifiable return endif - if visualmode() ==# "V" - execute "silent normal! gv\o0o$h" - echomsg "Switching to visual block mode for moving whole line(s) with MoveBlockRight" - return + let l:curr = virtcol('.') + let l:before = l:curr + a:distance + if !g:move_past_end_of_line + let l:before = max([1, min([l:before, virtcol('$')-1])]) endif - if visualmode() ==# "v" && a:lastline - a:firstline > 0 - execute "silent normal! gv\" - echomsg "Switching to visual block mode for moving multiple lines with MoveBlockRight" + if l:curr == l:before + " Don't add an empty change to the undo stack. return endif - + let l:old_default_register = @" let [l:old_virtualedit, &virtualedit] = [&virtualedit, 'all'] - call s:SaveDefaultRegister() - - " save previous cursor position - silent normal! gv - let l:row_pos = getcurpos()[1] - let l:is_rhs = virtcol(".") == l:max_col - - execute 'silent normal! gvd' . l:distance . "l" - " P behaves inconsistently in virtualedit 'all' mode; sometimes the cursor - " moves one right after pasting, other times it doesn't. This makes it - " difficult to rely on `[ to determine the start of the shifted selection. - let l:new_start_pos = virtcol(".") - execute 'silent normal! P' . l:new_start_pos . "|\`]" - - " restore previous cursor position - if getcurpos()[1] != l:row_pos - silent normal! o - if l:is_rhs - silent normal! O - endif - elseif !l:is_rhs - silent normal! O - endif - call s:RestoreDefaultRegister() + normal! x + execute 'normal!' . (l:before.'|') + normal! P + let &virtualedit = l:old_virtualedit + let @" = l:old_default_register + endfunction -function! s:MoveLineVertically(distance) +" +" In visual mode, move the selected block to the left +" Moves right (distance > 0) and left if (distance < 0). +" Switches to visual-block mode first if another visual mode is selected. +" +function! s:MoveBlockHorizontally(distance) if !&modifiable return endif - " Remember the current cursor position. When we move or reindent a line - " Vim will move the cursor to the first non-blank character. - let l:old_cursor_col = virtcol('.') - silent normal! ^ - let l:old_indent = virtcol('.') - - if a:distance <= 0 - let l:after = max([1, line('.') + a:distance]) - 1 - else - let l:after = min([line('$'), line('.') + a:distance]) - endif - execute 'silent move' l:after - - if g:move_auto_indent - silent normal! == + if visualmode() ==# 'V' + echomsg 'vim-move: Cannot move horizontally in linewise visual mode' + return endif - " Restore the cursor column, taking indentation changes into account. - let l:new_indent = virtcol('.') - let l:new_cursor_col = max([1, l:old_cursor_col - l:old_indent + l:new_indent]) - execute 'silent normal!' l:new_cursor_col . '|' -endfunction + normal! gv -function! s:MoveCharLeft(distance) - if !&modifiable || virtcol("$") == 1 || virtcol(".") == 1 - return + if visualmode() ==# 'v' + echomsg 'vim-move: Switching to visual block mode' + execute "normal! \" endif - call s:SaveDefaultRegister() + let l:cols = [virtcol("'<"), virtcol("'>")] + let l:first = min(l:cols) + let l:last = max(l:cols) + let l:width = l:last - l:first + 1 - if (virtcol('.') - a:distance <= 0) - silent normal! x0P - else - let [l:old_virtualedit, &virtualedit] = [&virtualedit, 'onemore'] - execute 'silent normal! x' . a:distance . 'hP' - let &virtualedit = l:old_virtualedit + let l:before = max([1, l:first + a:distance]) + if a:distance > 0 && !g:move_past_end_of_line + let l:shortest = min(map(getline("'<", "'>"), 'strwidth(v:val)')) + if l:last < l:shortest + let l:before = min([l:before, l:shortest - width + 1]) + else + let l:before = l:first + endif endif - call s:RestoreDefaultRegister() -endfunction - -function! s:MoveCharRight(distance) - if !&modifiable || virtcol("$") == 1 + if l:first == l:before + " Don't add an empty change to the undo stack. return endif - call s:SaveDefaultRegister() + let [l:old_virtualedit, &virtualedit] = [&virtualedit, 'all'] + let l:old_default_register = @" - if !g:move_past_end_of_line && (virtcol('.') + a:distance >= virtcol('$') - 1) - silent normal! x$p - else - let [l:old_virtualedit, &virtualedit] = [&virtualedit, 'all'] - execute 'silent normal! x' . a:distance . 'lP' - let &virtualedit = l:old_virtualedit - endif + normal! d + execute 'normal!' . (l:before.'|') + normal! P + + let @" = l:old_default_register + let &virtualedit = l:old_virtualedit + + " Reselect the pasted text. + " For some reason, `[ doesn't always point where it should -- sometimes it + " is off by one. Maybe it is because of the virtualedit=all? The + " workaround we found is to recompute the destination column by hand. + execute 'normal!' . (l:before.'|') . "\`]" - call s:RestoreDefaultRegister() endfunction + function! s:HalfPageSize() return winheight('.') / 2 endfunction @@ -220,24 +185,26 @@ function! s:MoveKey(key) return '<' . g:move_key_modifier . '-' . a:key . '>' endfunction +" Note: An older version of this program used callbacks with the "range" +" attribute to support being called with a selection range as a parameter. +" However, that had some problems: we would get E16 errors if the user tried +" to perform an out-of bounds move and the computations that used col() would +" also return the wrong results. Because of this, we have switched everything +" to using . -vnoremap MoveBlockDown :call MoveBlockVertically( v:count1) -vnoremap MoveBlockUp :call MoveBlockVertically(-v:count1) -vnoremap MoveBlockHalfPageDown :call MoveBlockVertically( v:count1 * HalfPageSize()) -vnoremap MoveBlockHalfPageUp :call MoveBlockVertically(-v:count1 * HalfPageSize()) -vnoremap MoveBlockLeft :call MoveBlockLeft(v:count1) -vnoremap MoveBlockRight :call MoveBlockRight(v:count1) +vnoremap MoveBlockDown : call MoveBlockVertically( v:count1) +vnoremap MoveBlockUp : call MoveBlockVertically(-v:count1) +vnoremap MoveBlockHalfPageDown : call MoveBlockVertically( v:count1 * HalfPageSize()) +vnoremap MoveBlockHalfPageUp : call MoveBlockVertically(-v:count1 * HalfPageSize()) +vnoremap MoveBlockRight : call MoveBlockHorizontally( v:count1) +vnoremap MoveBlockLeft : call MoveBlockHorizontally(-v:count1) -" We can't use functions defined with the 'range' attribute for moving lines -" or characters. In the case of lines, it causes vim to complain with E16 -" (Invalid adress) if we try to move out of bounds. In the case of characters, -" it messes up the result of calling col(). nnoremap MoveLineDown : call MoveLineVertically( v:count1) nnoremap MoveLineUp : call MoveLineVertically(-v:count1) nnoremap MoveLineHalfPageDown : call MoveLineVertically( v:count1 * HalfPageSize()) nnoremap MoveLineHalfPageUp : call MoveLineVertically(-v:count1 * HalfPageSize()) -nnoremap MoveCharLeft : call MoveCharLeft(v:count1) -nnoremap MoveCharRight : call MoveCharRight(v:count1) +nnoremap MoveCharRight : call MoveCharHorizontally( v:count1) +nnoremap MoveCharLeft : call MoveCharHorizontally(-v:count1) if g:move_map_keys