mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-01-24 05:50:05 +08:00
417 lines
12 KiB
VimL
417 lines
12 KiB
VimL
""
|
|
" This variable is used to inform the s:step_*() functions about whether the
|
|
" current movement is a cursor movement or a scroll movement. Used for
|
|
" motions like gg and G
|
|
let s:cursor_movement = v:false
|
|
|
|
""
|
|
" This variable is needed to let the s:step_down() function know whether to
|
|
" continue scrolling after reaching EOL (as in ^F) or not (^B, ^D, ^U, etc.)
|
|
"
|
|
" NOTE: This variable "MUST" be set to v:false in "every" function that
|
|
" invokes motion (except smoothie#forwards, where it must be set to v:true)
|
|
let s:ctrl_f_invoked = v:false
|
|
|
|
if !exists('g:smoothie_enabled')
|
|
""
|
|
" Set it to 0 to disable vim-smoothie. Useful for very slow connections.
|
|
let g:smoothie_enabled = 1
|
|
endif
|
|
|
|
if !exists('g:smoothie_update_interval')
|
|
""
|
|
" Time (in milliseconds) between subsequent screen/cursor position updates.
|
|
" Lower value produces smoother animation. Might be useful to increase it
|
|
" when running Vim over low-bandwidth/high-latency connections.
|
|
let g:smoothie_update_interval = 20
|
|
endif
|
|
|
|
if !exists('g:smoothie_speed_constant_factor')
|
|
""
|
|
" This value controls constant term of the velocity curve. Increasing this
|
|
" boosts primarily cursor speed at the end of animation.
|
|
let g:smoothie_speed_constant_factor = 10
|
|
endif
|
|
|
|
if !exists('g:smoothie_speed_linear_factor')
|
|
""
|
|
" This value controls linear term of the velocity curve. Increasing this
|
|
" boosts primarily cursor speed at the beginning of animation.
|
|
let g:smoothie_speed_linear_factor = 10
|
|
endif
|
|
|
|
if !exists('g:smoothie_speed_exponentiation_factor')
|
|
""
|
|
" This value controls exponent of the power function in the velocity curve.
|
|
" Generally should be less or equal to 1.0. Lower values produce longer but
|
|
" perceivably smoother animation.
|
|
let g:smoothie_speed_exponentiation_factor = 0.9
|
|
endif
|
|
|
|
if !exists('g:smoothie_break_on_reverse')
|
|
""
|
|
" Stop immediately if we're moving and the user requested moving in opposite
|
|
" direction. It's mostly useful at very low scrolling speeds, hence
|
|
" disabled by default.
|
|
let g:smoothie_break_on_reverse = 0
|
|
endif
|
|
|
|
""
|
|
" Execute {command}, but saving 'scroll' value before, and restoring it
|
|
" afterwards. Useful for some commands (such as ^D or ^U), which overwrite
|
|
" 'scroll' permanently if used with a [count].
|
|
"
|
|
" Additionally, this function temporarily clears 'scrolloff' and resets it
|
|
" after command execution. This is workaround for a bug described in
|
|
" https://github.com/psliwka/vim-smoothie/issues/18
|
|
function s:execute_preserving_scroll(command)
|
|
let l:saved_scroll = &scroll
|
|
let l:saved_scrolloff = 0
|
|
if &scrolloff
|
|
let l:saved_scrolloff = &scrolloff
|
|
let &scrolloff = 0
|
|
endif
|
|
execute a:command
|
|
let &scroll = l:saved_scroll
|
|
if l:saved_scrolloff
|
|
let &scrolloff = l:saved_scrolloff
|
|
endif
|
|
endfunction
|
|
|
|
""
|
|
" Scroll the window up by one line, or move the cursor up if the window is
|
|
" already at the top. Return 1 if cannot move any higher.
|
|
function s:step_up()
|
|
if line('.') > 1
|
|
if s:cursor_movement
|
|
exe 'normal! k'
|
|
return 0
|
|
endif
|
|
call s:execute_preserving_scroll("normal! 1\<C-U>")
|
|
return 0
|
|
else
|
|
return 1
|
|
endif
|
|
endfunction
|
|
|
|
""
|
|
" Scroll the window down by one line, or move the cursor down if the window is
|
|
" already at the bottom. Return 1 if cannot move any lower.
|
|
function s:step_down()
|
|
let l:initial_winline = winline()
|
|
|
|
if line('.') < line('$')
|
|
if s:cursor_movement
|
|
exe 'normal! j'
|
|
return 0
|
|
endif
|
|
" NOTE: the three lines of code following this comment block
|
|
" have been implemented as a temporary workaround for a vim issue
|
|
" regarding Ctrl-D and folds.
|
|
"
|
|
" See: neovim/neovim#13080
|
|
if foldclosedend('.') != -1
|
|
call cursor(foldclosedend('.'), col('.'))
|
|
endif
|
|
call s:execute_preserving_scroll("normal! 1\<C-D>")
|
|
if s:ctrl_f_invoked && winline() > l:initial_winline
|
|
" ^F is pressed, and the last motion caused cursor postion to change
|
|
" scroll window to keep cursor position fixed
|
|
call s:execute_preserving_scroll("normal! \<C-E>")
|
|
endif
|
|
return 0
|
|
|
|
elseif s:ctrl_f_invoked && winline() > 1
|
|
" cursor is already on last line of buffer, but not on last line of window
|
|
" ^F can scroll more
|
|
call s:execute_preserving_scroll("normal! \<C-E>")
|
|
return 0
|
|
|
|
else
|
|
return 1
|
|
endif
|
|
endfunction
|
|
|
|
""
|
|
" Perform as many steps up or down to move {lines} lines from the starting
|
|
" position (negative {lines} value means to go up). Return 1 if hit either
|
|
" top or bottom, and cannot move further.
|
|
function s:step_many(lines)
|
|
let l:remaining_lines = a:lines
|
|
while 1
|
|
if l:remaining_lines < 0
|
|
if s:step_up()
|
|
return 1
|
|
endif
|
|
let l:remaining_lines += 1
|
|
elseif l:remaining_lines > 0
|
|
if s:step_down()
|
|
return 1
|
|
endif
|
|
let l:remaining_lines -= 1
|
|
else
|
|
return 0
|
|
endif
|
|
endwhile
|
|
endfunction
|
|
|
|
""
|
|
" A Number indicating how many lines do we need yet to move down (or up, if
|
|
" it's negative), to achieve what the user wants.
|
|
let s:target_displacement = 0
|
|
|
|
""
|
|
" A Float between -1.0 and 1.0 keeping our position between integral lines,
|
|
" used to make the animation smoother.
|
|
let s:subline_position = 0.0
|
|
|
|
""
|
|
" Start the animation timer if not already running. Should be called when
|
|
" updating the target, when there's a chance we're not already moving.
|
|
function s:start_moving()
|
|
if ((s:target_displacement < 0) ? line('.') == 1 : (line('.') == line('$') && (s:ctrl_f_invoked ? winline() == 1 : v:true)))
|
|
" Invalid command
|
|
call s:ring_bell()
|
|
endif
|
|
if !exists('s:timer_id')
|
|
let s:timer_id = timer_start(g:smoothie_update_interval, function('s:movement_tick'), {'repeat': -1})
|
|
endif
|
|
endfunction
|
|
|
|
""
|
|
" Stop any movement immediately, and disable the animation timer to conserve
|
|
" power.
|
|
function s:stop_moving()
|
|
let s:target_displacement = 0
|
|
let s:subline_position = 0.0
|
|
if exists('s:timer_id')
|
|
call timer_stop(s:timer_id)
|
|
unlet s:timer_id
|
|
endif
|
|
endfunction
|
|
|
|
""
|
|
" Calculate optimal movement velocity (in lines per second, negative value
|
|
" means to move upwards) for the next animation frame.
|
|
"
|
|
" TODO: current algorithm is rather crude, would be good to research better
|
|
" alternatives.
|
|
function s:compute_velocity()
|
|
let l:absolute_speed = g:smoothie_speed_constant_factor + g:smoothie_speed_linear_factor * pow(abs(s:target_displacement - s:subline_position), g:smoothie_speed_exponentiation_factor)
|
|
if s:target_displacement < 0
|
|
return -l:absolute_speed
|
|
else
|
|
return l:absolute_speed
|
|
endif
|
|
endfunction
|
|
|
|
""
|
|
" Execute single animation frame. Called periodically by a timer. Accepts a
|
|
" throwaway parameter: the timer ID.
|
|
function s:movement_tick(_)
|
|
if s:target_displacement == 0
|
|
call s:stop_moving()
|
|
return
|
|
endif
|
|
|
|
let l:subline_step_size = s:subline_position + (g:smoothie_update_interval/1000.0 * s:compute_velocity())
|
|
let l:step_size = float2nr(trunc(l:subline_step_size))
|
|
|
|
if abs(l:step_size) > abs(s:target_displacement)
|
|
" clamp step size to prevent overshooting the target
|
|
let l:step_size = s:target_displacement
|
|
end
|
|
|
|
if s:step_many(l:step_size)
|
|
" we've collided with either buffer end
|
|
call s:stop_moving()
|
|
else
|
|
let s:target_displacement -= l:step_size
|
|
let s:subline_position = l:subline_step_size - l:step_size
|
|
endif
|
|
|
|
if l:step_size
|
|
" Usually Vim handles redraws well on its own, but without explicit redraw
|
|
" I've encountered some sporadic display artifacts. TODO: debug further.
|
|
redraw
|
|
endif
|
|
endfunction
|
|
|
|
""
|
|
" Set a new target where we should move to (in lines, relative to our current
|
|
" position). If we're already moving, try to do the smart thing, taking into
|
|
" account our progress in reaching the target set previously.
|
|
function s:update_target(lines)
|
|
if g:smoothie_break_on_reverse && s:target_displacement * a:lines < 0
|
|
call s:stop_moving()
|
|
else
|
|
" Cursor movements are very delicate. Since the displacement for cursor
|
|
" movements is calulated from the "current" line, so immediately stop
|
|
" moving, otherwise we will end up at the wrong line.
|
|
if s:cursor_movement
|
|
call s:stop_moving()
|
|
endif
|
|
let s:target_displacement += a:lines
|
|
call s:start_moving()
|
|
endif
|
|
endfunction
|
|
|
|
""
|
|
" Helper function to calculate the actual number of screen lines from a line
|
|
" to another. Useful for properly handling folds in case of cursor movements.
|
|
function s:calculate_screen_lines(from, to)
|
|
let l:from = a:from
|
|
let l:to = a:to
|
|
let l:from = (foldclosed(l:from) != -1 ? foldclosed(l:from) : l:from)
|
|
let l:to = (foldclosed(l:to) != -1 ? foldclosed(l:to) : l:to)
|
|
if l:from == l:to
|
|
return 0
|
|
endif
|
|
let l:lines = 0
|
|
let l:linenr = l:from
|
|
while l:linenr != l:to
|
|
if l:linenr < l:to
|
|
let l:lines +=1
|
|
let l:linenr = (foldclosedend(l:linenr) != -1 ? foldclosedend(l:linenr) : l:linenr)
|
|
let l:linenr += 1
|
|
elseif l:linenr > l:to
|
|
let l:lines -= 1
|
|
let l:linenr = (foldclosed(l:linenr) != -1 ? foldclosed(l:linenr) : l:linenr)
|
|
let l:linenr -= 1
|
|
endif
|
|
endwhile
|
|
return l:lines
|
|
endfunction
|
|
|
|
""
|
|
" Helper function to set 'scroll' to [count], similarly to what native ^U and
|
|
" ^D commands do.
|
|
function s:count_to_scroll()
|
|
if v:count
|
|
let &scroll=v:count
|
|
end
|
|
endfunction
|
|
|
|
""
|
|
" Helper function to ring bell.
|
|
function s:ring_bell()
|
|
if !(&belloff =~# 'all\|error')
|
|
let l:belloff = &belloff
|
|
set belloff=
|
|
exe "normal \<Esc>"
|
|
let &belloff = l:belloff
|
|
endif
|
|
endfunction
|
|
|
|
""
|
|
" Smooth equivalent to ^D.
|
|
function smoothie#downwards()
|
|
if !g:smoothie_enabled
|
|
exe "normal! \<C-d>"
|
|
return
|
|
endif
|
|
let s:ctrl_f_invoked = v:false
|
|
call s:count_to_scroll()
|
|
call s:update_target(&scroll)
|
|
endfunction
|
|
|
|
""
|
|
" Smooth equivalent to ^U.
|
|
function smoothie#upwards()
|
|
if !g:smoothie_enabled
|
|
exe "normal! \<C-u>"
|
|
return
|
|
endif
|
|
let s:ctrl_f_invoked = v:false
|
|
call s:count_to_scroll()
|
|
call s:update_target(-&scroll)
|
|
endfunction
|
|
|
|
""
|
|
" Smooth equivalent to ^F.
|
|
function smoothie#forwards()
|
|
if !g:smoothie_enabled
|
|
exe "normal! \<C-f>"
|
|
return
|
|
endif
|
|
let s:ctrl_f_invoked = v:true
|
|
call s:update_target(winheight(0) * v:count1)
|
|
endfunction
|
|
|
|
""
|
|
" Smooth equivalent to ^B.
|
|
function smoothie#backwards()
|
|
if !g:smoothie_enabled
|
|
exe "normal! \<C-b>"
|
|
return
|
|
endif
|
|
let s:ctrl_f_invoked = v:false
|
|
call s:update_target(-winheight(0) * v:count1)
|
|
endfunction
|
|
|
|
""
|
|
" Smoothie equivalent for G and gg
|
|
" NOTE: I have also added - movement to dempnstrate how to add more new
|
|
" movements in the future
|
|
function smoothie#cursor_movement(movement)
|
|
let l:movements = {
|
|
\'gg': {
|
|
\'target_expr': 'v:count1',
|
|
\'startofline': &startofline,
|
|
\'jump_commmand': v:true,
|
|
\},
|
|
\'G' : {
|
|
\'target_expr': "(v:count ? v:count : line('$'))",
|
|
\'startofline': &startofline,
|
|
\'jump_commmand': v:true,
|
|
\},
|
|
\'-' : {
|
|
\'target_expr': "line('.') - v:count1",
|
|
\'startofline': v:true,
|
|
\'jump_commmand': v:false,
|
|
\},
|
|
\}
|
|
if !has_key(l:movements, a:movement)
|
|
return 1
|
|
endif
|
|
call s:do_vertical_cursor_movement(a:movement, l:movements[a:movement])
|
|
endfunction
|
|
|
|
""
|
|
" Helper function to preform cursor movements
|
|
function s:do_vertical_cursor_movement(movement, properties)
|
|
let s:cursor_movement = v:true
|
|
let s:ctrl_f_invoked = v:false
|
|
" If in operator pending mode, disable vim-smoothie and use the normal
|
|
" non-smoothie version of the movement
|
|
if !g:smoothie_enabled || mode(1) =~# 'o' && mode(1) =~? 'no'
|
|
" If in operator-pending mode, prefer the movement to be linewise
|
|
exe 'normal! ' . (mode(1) ==# 'no' ? 'V' : '') . v:count . a:movement
|
|
return
|
|
endif
|
|
let l:target = eval(a:properties['target_expr'])
|
|
let l:target = (l:target > line('$') ? line('$') : l:target)
|
|
let l:target = (foldclosed(l:target) != -1 ? foldclosed(l:target) : l:target)
|
|
if foldclosed('.') == l:target
|
|
let s:cursor_movement = v:false
|
|
return
|
|
endif
|
|
" if this is a jump command, append current position to the jumplist
|
|
if a:properties['jump_commmand']
|
|
execute "normal! m'"
|
|
endif
|
|
call s:update_target(s:calculate_screen_lines(line('.'), l:target))
|
|
" suspend further commands till the destination is reached
|
|
" see point (3) of https://github.com/psliwka/vim-smoothie/issues/1#issuecomment-560158642
|
|
while line('.') != l:target
|
|
exe 'sleep ' . g:smoothie_update_interval . ' m'
|
|
endwhile
|
|
let s:cursor_movement = v:false " reset s:cursor_movement to false
|
|
if a:properties['startofline']
|
|
" move cursor to the first non-blank character of the line
|
|
call cursor(line('.'), match(getline('.'),'\S')+1)
|
|
endif
|
|
endfunction
|
|
|
|
" vim: et ts=2
|