diff --git a/doc/move.txt b/doc/move.txt index ed6348b..34fd581 100644 --- a/doc/move.txt +++ b/doc/move.txt @@ -20,10 +20,31 @@ wrapping the :move command. =============================================================================== 2. Mappings *move-mappings* -To enable custom key maps you must disable the automatic key maps with > +To enable/disable automatic key maps you can define the variable +g:move_map_keys in to 1 or 0 respectively. IE, if you want to define your own +key mappings you would put in your vimrc: > let g:move_map_keys = 0 +The default value of g:move_map_keys is 1 so all mappings are usually defined. + +The automatic key maps also can be enabled/disabled by parts defining +g:move_map_keys as a dictionary instad of an integer. This can be +done through the entries 'vertical' and 'horizontal' each of which should be +also a dictionary with the integer (boolean) entries 'normal' and 'visual'. +With that in mind, if you want to have the vertical mappings only enabled in +visual mode but the horizontal mappings to be enabled in both modes you can +define g:move_map_keys as: > + + let g:move_map_keys = {} + let g:move_map_keys.vertical = {'normal': 0, 'visual': 1} + let g:move_map_keys.horizontal = {'normal': 1, 'visual': 1} + +Or, as the default values of all entries are also 1, it can be simply +written as: > + + let g:move_map_keys = {'vertical': {'normal': 0}} + The plugin provide finger-friendly mappings to move text around by using keys. You can specify the key modifier that is used in key bindings with > @@ -141,6 +162,12 @@ license. =============================================================================== 3. Changelog *move-changelog* +v1.4.1 + * Bug corrections from 1.4 + * If vertical movement receives a count past end of file, it moves to the + end instead of staying in place. + * The standard mappings can be enabled by parts through a dictionary. + v1.4 * Released on 08/19/18 * New functionality for moving horizontally diff --git a/plugin/move.vim b/plugin/move.vim index c4a3fb3..6e6071d 100644 --- a/plugin/move.vim +++ b/plugin/move.vim @@ -1,46 +1,66 @@ " ============================================================================= " File: plugin/move.vim -" Description: Move lines and selections up and even down. +" Description: Move lines and selections up, down, left and even right to the +" infinity. " Author: Matthias Vogelgesang " ============================================================================= if exists('g:loaded_move') || &compatible finish endif - let g:loaded_move = 1 -if !exists('g:move_map_keys') - let g:move_map_keys = 1 -endif +" =====[ Configuration Variables ]================ +function! s:SetDefaultValue(var, val) + if !exists(a:var) + execute 'let' a:var '=' string(a:val) + endif +endfunction -if !exists('g:move_key_modifier') - let g:move_key_modifier = 'A' -endif +call s:SetDefaultValue('g:move_map_keys', 1) +call s:SetDefaultValue('g:move_key_modifier', 'A') +call s:SetDefaultValue('g:move_auto_indent', 1) +call s:SetDefaultValue('g:move_map_keys', 1) +call s:SetDefaultValue('g:move_past_end_of_line', 1) -if !exists('g:move_auto_indent') - let g:move_auto_indent = 1 +if type(g:move_map_keys) == type({}) + call s:SetDefaultValue('g:move_map_keys.vertical', {}) + call s:SetDefaultValue('g:move_map_keys.vertical.normal', 1) + call s:SetDefaultValue('g:move_map_keys.vertical.visual', 1) + call s:SetDefaultValue('g:move_map_keys.horizontal', {}) + call s:SetDefaultValue('g:move_map_keys.horizontal.normal', 1) + call s:SetDefaultValue('g:move_map_keys.horizontal.visual', 1) endif -if !exists('g:move_past_end_of_line') - let g:move_past_end_of_line = 1 -endif +" =====[ Script local variables ]================= +let s:command_blockwise_selection = "\" +" After deleting and pasting "gv" would select the old position but the +" following command selects the position of the pasted text +let s:command_select_after_P = '`[' . s:command_blockwise_selection . '`]' +" =====[ Utility functions ]====================== function! s:ResetCursor() normal! gv=gv^ endfunction -function! s:MoveBlockDown(start, end, count) - let l:next_line = a:end + a:count - - if v:count > 0 - let l:next_line = l:next_line + v:count - 1 +function! s:AssertBlockwiseVisual(mode) + if a:mode ==# 'v' + execute 'normal!' s:command_blockwise_selection endif +endfunction - if l:next_line > line('$') - call s:ResetCursor() - return - endif +function! s:HalfWin() + return winheight('.') / 2 +endfunction + +function! s:MoveKey(key) + return '<' . g:move_key_modifier . '-' . a:key . '>' +endfunction + +" =====[ Functionality ]========================== +function! s:MoveBlockDown(start, end, num) + let l:next_line = a:end + a:num + max([0, v:count - 1]) + let l:next_line = min([line('$'), l:next_line]) execute 'silent' a:start ',' a:end 'move ' l:next_line if (g:move_auto_indent == 1) @@ -50,17 +70,9 @@ function! s:MoveBlockDown(start, end, count) endif endfunction -function! s:MoveBlockUp(start, end, count) - let l:prev_line = a:start - a:count - 1 - - if v:count > 0 - let l:prev_line = l:prev_line - v:count + 1 - endif - - if l:prev_line < 0 - call s:ResetCursor() - return - endif +function! s:MoveBlockUp(start, end, num) + let l:prev_line = a:start - a:num - 1 - max([0, v:count - 1]) + let l:prev_line = max([0, l:prev_line]) execute 'silent' a:start ',' a:end 'move ' l:prev_line if (g:move_auto_indent == 1) @@ -71,36 +83,35 @@ function! s:MoveBlockUp(start, end, count) endfunction function! s:MoveBlockLeft() range - if visualmode() ==# "\" - echomsg 'MoveBlockLeft can only be used in visual block' - endif + call s:AssertBlockwiseVisual(visualmode()) let l:distance = v:count ? v:count : 1 + let l:min_col = min([col("'<"), col("'>")]) - let l:min_col = min([col("'<"), col("'>")]) - + " Having 'virtualenv' set to 'onemore' fixes problem of one more movement + " that needed when moving block from end of line to the left let [l:old_virtualedit, &virtualedit] = [&virtualedit, 'onemore'] + + let l:move_command = 'silent normal! gvd' if l:min_col - l:distance <= 1 - execute "silent normal! gvd0P`[\`]" + let l:move_command .= '0P' else - execute 'silent normal! gvd' . l:distance . "hP`[\`]" + let l:move_command .= l:distance . 'hP' endif + execute l:move_command . s:command_select_after_P let &virtualedit = l:old_virtualedit endfunction function! s:MoveBlockRight() range - if visualmode() ==# "\" - echomsg 'MoveBlockLeft can only be used in visual block' - endif + call s:AssertBlockwiseVisual(visualmode()) let l:distance = v:count ? v:count : 1 - let l:lens = map(getline(a:firstline, a:lastline), 'len(v:val)') - let [l:shorter_line_len, l:longer_line_len] = [min(l:lens), max(l:lens)] + let l:shorter_line_len = min(l:lens) - let l:are_same_lines = col("'<") == col("'>") - let l:max_col = max([col("'<"), col("'>")]) + let l:are_same_cols = (col("'<") == col("'>")) + let l:max_col = max([col("'<"), col("'>")]) if !g:move_past_end_of_line && (l:max_col + l:distance >= l:shorter_line_len) let l:distance = l:shorter_line_len - l:max_col @@ -112,67 +123,54 @@ function! s:MoveBlockRight() range endif let [l:old_virtualedit, &virtualedit] = [&virtualedit, 'all'] - execute 'silent normal! gvd' . l:distance . "lP`[\`]" - " Very strange things happen with 'virtualedit' set to all. One of the is that - " the selection loses one column at the left at reselection. - " The next line fixes it - if !l:are_same_lines && (l:max_col + l:distance < l:longer_line_len) + execute 'silent normal! gvd' . l:distance . 'lP' . s:command_select_after_P + + " When 'virtualedit' is set to all the selection loses one column at the + " left at reselection. The next line fixes it + if !l:are_same_cols && (l:max_col + l:distance < l:lens[0]) normal! oho endif - let &virtualedit = l:old_virtualedit + let &virtualedit = l:old_virtualedit endfunction -function! s:MoveLineUp(count) range - let l:distance = a:count + 1 - - if v:count > 0 - let l:distance = l:distance + v:count - 1 - endif +function! s:MoveLineUp(count) + let l:distance = a:count + 1 + max([0, v:count - 1]) + let l:command = 'silent move ' if (line('.') - l:distance) < 0 - execute 'silent move 0' - if (g:move_auto_indent == 1) - normal! == - endif - return + let l:command .= '0' + else + let l:command .= '-' . l:distance endif - - execute 'silent m-' . l:distance + execute l:command if (g:move_auto_indent == 1) normal! == endif endfunction -function! s:MoveLineDown(count) range - let l:distance = a:count - - if v:count > 0 - let l:distance = l:distance + v:count - 1 - endif +function! s:MoveLineDown(count) + let l:distance = a:count + max([0, v:count - 1]) + let l:command = 'silent move ' if (line('.') + l:distance) > line('$') - silent move $ - if (g:move_auto_indent == 1) - normal! == - endif - return + let l:command .= '$' + else + let l:command .= '+' . l:distance endif + execute l:command - execute 'silent m+' . l:distance if (g:move_auto_indent == 1) normal! == endif endfunction -" Using range here fucks the col() function (because col() always returns 1 in -" range functions), so use normal function and clear the range with later function! s:MoveCharLeft() let l:distance = v:count ? v:count : 1 - if (col('.') - l:distance <= 0) + if (col('.') - l:distance <= 1) silent normal! x0P return endif @@ -195,62 +193,58 @@ function! s:MoveCharRight() let &virtualedit = l:old_virtualedit endfunction -function! s:MoveBlockOneLineUp() range - call s:MoveBlockUp(a:firstline, a:lastline, 1) -endfunction - -function! s:MoveBlockOneLineDown() range - call s:MoveBlockDown(a:firstline, a:lastline, 1) -endfunction - -function! s:MoveBlockHalfPageUp() range - let l:distance = winheight('.') / 2 - call s:MoveBlockUp(a:firstline, a:lastline, l:distance) -endfunction - -function! s:MoveBlockHalfPageDown() range - let l:distance = winheight('.') / 2 - call s:MoveBlockDown(a:firstline, a:lastline, l:distance) +function! s:MoveBlockNumDown(num) range + call s:MoveBlockDown(a:firstline, a:lastline, a:num) endfunction -function! s:MoveLineHalfPageUp() range - let l:distance = winheight('.') / 2 - call s:MoveLineUp(l:distance) +function! s:MoveBlockNumUp(num) range + call s:MoveBlockUp(a:firstline, a:lastline, a:num) endfunction -function! s:MoveLineHalfPageDown() range - let l:distance = winheight('.') / 2 - call s:MoveLineDown(l:distance) -endfunction - -function! s:MoveKey(key) - return '<' . g:move_key_modifier . '-' . a:key . '>' -endfunction - - -vnoremap MoveBlockDown :call MoveBlockOneLineDown() -vnoremap MoveBlockUp :call MoveBlockOneLineUp() -vnoremap MoveBlockHalfPageDown :call MoveBlockHalfPageDown() -vnoremap MoveBlockHalfPageUp :call MoveBlockHalfPageUp() +" =====[ API ]=================================== +vnoremap MoveBlockDown :call MoveBlockNumDown(1) +vnoremap MoveBlockUp :call MoveBlockNumUp(1) +vnoremap MoveBlockHalfPageDown :call MoveBlockNumDown(s:HalfWin()) +vnoremap MoveBlockHalfPageUp :call MoveBlockNumUp(s:HalfWin()) vnoremap MoveBlockLeft :call MoveBlockLeft() vnoremap MoveBlockRight :call MoveBlockRight() -nnoremap MoveLineDown :call MoveLineDown(1) -nnoremap MoveLineUp :call MoveLineUp(1) -nnoremap MoveLineHalfPageDown :call MoveLineHalfPageDown() -nnoremap MoveLineHalfPageUp :call MoveLineHalfPageUp() +nnoremap MoveLineDown :call MoveLineDown(1) +nnoremap MoveLineUp :call MoveLineUp(1) +nnoremap MoveLineHalfPageDown :call MoveLineDown(s:HalfWin()) +nnoremap MoveLineHalfPageUp :call MoveLineUp(s:HalfWin()) nnoremap MoveCharLeft :call MoveCharLeft() nnoremap MoveCharRight :call MoveCharRight() +function! s:UserWantMap(movement, mode) + " In vim 8, v:t_number can be used instead of type(0) and v:t_dict instead + " of type({}), but at the cost of losing compatibility with previous + " versions + if type(g:move_map_keys) == type(0) + return g:move_map_keys != 0 + endif -if g:move_map_keys - execute 'vmap' s:MoveKey('j') 'MoveBlockDown' - execute 'vmap' s:MoveKey('k') 'MoveBlockUp' - execute 'vmap' s:MoveKey('h') 'MoveBlockLeft' - execute 'vmap' s:MoveKey('l') 'MoveBlockRight' + if type(g:move_map_keys) == type({}) + return g:move_map_keys[a:movement][a:mode] != 0 + endif +endfunction + +if s:UserWantMap('vertical', 'visual') + execute 'xmap' s:MoveKey('j') 'MoveBlockDown' + execute 'xmap' s:MoveKey('k') 'MoveBlockUp' +endif +if s:UserWantMap('horizontal', 'visual') + execute 'xmap' s:MoveKey('h') 'MoveBlockLeft' + execute 'xmap' s:MoveKey('l') 'MoveBlockRight' +endif + +if s:UserWantMap('vertical', 'normal') execute 'nmap' s:MoveKey('j') 'MoveLineDown' execute 'nmap' s:MoveKey('k') 'MoveLineUp' +endif + +if s:UserWantMap('horizontal', 'normal') execute 'nmap' s:MoveKey('h') 'MoveCharLeft' execute 'nmap' s:MoveKey('l') 'MoveCharRight' endif