vim-easymotion/plugin/EasyMotion.vim

446 lines
12 KiB
VimL
Raw Normal View History

2011-03-28 04:18:00 -04:00
" EasyMotion - Vim motions on speed!
2011-03-27 18:08:06 -04:00
"
2011-03-29 08:10:00 -04:00
" Author: Kim Silkebækken <kim.silkebaekken+vim@gmail.com>
" Source repository: https://github.com/Lokaltog/vim-easymotion
2011-03-27 18:08:06 -04:00
2011-03-30 08:18:39 -04:00
" Script initialization {{{
if exists('g:EasyMotion_loaded') || &compatible || version < 702
2011-03-27 18:08:06 -04:00
finish
endif
let g:EasyMotion_loaded = 1
" }}}
" Default configuration {{{
function! s:InitOptions(options) " {{{
for [key, value] in items(a:options)
if ! exists('g:EasyMotion_' . key)
exec 'let g:EasyMotion_' . key . ' = ' . string(value)
endif
endfor
endfunction " }}}
function! s:InitHL(group, colors) " {{{
2011-03-31 09:39:58 -04:00
let guihl = printf('guibg=%s guifg=%s gui=%s', a:colors.gui[0], a:colors.gui[1], a:colors.gui[2])
let ctermhl = &t_Co == 256
\ ? printf('ctermbg=%s ctermfg=%s cterm=%s', a:colors.cterm256[0], a:colors.cterm256[1], a:colors.cterm256[2])
\ : printf('ctermbg=%s ctermfg=%s cterm=%s', a:colors.cterm[0], a:colors.cterm[1], a:colors.cterm[2])
execute printf('hi default %s %s %s', a:group, guihl, ctermhl)
endfunction " }}}
2011-04-01 01:46:42 -04:00
function! s:InitMappings(motions) "{{{
for motion in keys(a:motions)
call s:InitOptions({ 'mapping_' . motion : '<Leader>' . motion })
2011-04-01 01:46:42 -04:00
endfor
if g:EasyMotion_do_mapping
for [motion, fn] in items(a:motions)
2011-04-01 02:48:33 -04:00
silent exec 'nnoremap <silent> ' . g:EasyMotion_mapping_{motion} . ' :call EasyMotion' . fn.name . '(0, ' . fn.dir . ')<CR>'
silent exec 'onoremap <silent> ' . g:EasyMotion_mapping_{motion} . ' :call EasyMotion' . fn.name . '(0, ' . fn.dir . ')<CR>'
silent exec 'vnoremap <silent> ' . g:EasyMotion_mapping_{motion} . ' :<C-U>call EasyMotion' . fn.name . '(1, ' . fn.dir . ')<CR>'
2011-04-01 01:46:42 -04:00
endfor
endif
endfunction "}}}
" Default options {{{
call s:InitOptions({
\ 'keys' : 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
\ , 'target_hl' : 'EasyMotionTarget'
\ , 'shade_hl' : 'EasyMotionShade'
\ , 'do_shade' : 1
\ , 'do_mapping' : 1
\ })
" }}}
" Default highlighting {{{
let s:target_hl_defaults = {
2011-03-31 09:39:58 -04:00
\ 'gui' : ['NONE', '#ff0000' , 'bold']
\ , 'cterm256': ['NONE', '196' , 'bold']
\ , 'cterm' : ['NONE', 'red' , 'bold']
\ }
let s:shade_hl_defaults = {
2011-03-31 09:39:58 -04:00
\ 'gui' : ['NONE', '#585858' , 'NONE']
\ , 'cterm256': ['NONE', '240' , 'NONE']
\ , 'cterm' : ['NONE', 'darkgrey', 'NONE']
\ }
call s:InitHL(g:EasyMotion_target_hl, s:target_hl_defaults)
call s:InitHL(g:EasyMotion_shade_hl, s:shade_hl_defaults)
" Reset highlighting after loading a new color scheme {{{
augroup EasyMotionInitHL
autocmd!
autocmd ColorScheme * call s:InitHL(g:EasyMotion_target_hl, s:target_hl_defaults)
autocmd ColorScheme * call s:InitHL(g:EasyMotion_shade_hl, s:shade_hl_defaults)
augroup end
" }}}
" }}}
2011-04-01 01:46:42 -04:00
" Default key mapping {{{
call s:InitMappings({
\ 'f' : { 'name': 'F' , 'dir': 0 }
\ , 'F' : { 'name': 'F' , 'dir': 1 }
\ , 't' : { 'name': 'T' , 'dir': 0 }
\ , 'T' : { 'name': 'T' , 'dir': 1 }
\ , 'w' : { 'name': 'WB', 'dir': 0 }
\ , 'b' : { 'name': 'WB', 'dir': 1 }
\ , 'e' : { 'name': 'E' , 'dir': 0 }
\ , 'ge': { 'name': 'E' , 'dir': 1 }
\ , 'j' : { 'name': 'JK', 'dir': 0 }
\ , 'k' : { 'name': 'JK', 'dir': 1 }
\ })
" }}}
2011-03-27 18:08:06 -04:00
" }}}
" Motion functions {{{
function! EasyMotionF(visualmode, direction) " {{{
let char = s:GetSearchChar(a:visualmode)
2011-03-27 18:08:06 -04:00
if empty(char)
return
endif
2011-03-27 18:08:06 -04:00
let re = '\C' . escape(char, '.$^~')
2011-03-27 18:08:06 -04:00
call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', mode(1))
endfunction " }}}
function! EasyMotionT(visualmode, direction) " {{{
let char = s:GetSearchChar(a:visualmode)
if empty(char)
return
endif
2011-03-27 18:08:06 -04:00
if a:direction == 1
let re = '\C' . escape(char, '.$^~') . '\zs.'
else
let re = '\C.' . escape(char, '.$^~')
endif
2011-03-27 18:08:06 -04:00
call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', mode(1))
endfunction " }}}
2011-03-30 12:22:42 -04:00
function! EasyMotionWB(visualmode, direction) " {{{
call s:EasyMotion('\<.', a:direction, a:visualmode ? visualmode() : '', '')
endfunction " }}}
2011-03-30 12:03:48 -04:00
function! EasyMotionE(visualmode, direction) " {{{
2011-04-01 02:07:42 -04:00
call s:EasyMotion('.\>', a:direction, a:visualmode ? visualmode() : '', mode(1))
endfunction " }}}
2011-03-30 12:47:02 -04:00
function! EasyMotionJK(visualmode, direction) " {{{
call s:EasyMotion('\%1v', a:direction, a:visualmode ? visualmode() : '', '')
2011-03-30 12:47:02 -04:00
endfunction " }}}
2011-03-27 18:08:06 -04:00
" }}}
" Helper functions {{{
function! s:Message(message) " {{{
echo 'EasyMotion: ' . a:message
endfunction " }}}
function! s:Prompt(message) " {{{
echohl Question
echo a:message . ': '
echohl None
endfunction " }}}
function! s:VarReset(var, ...) " {{{
2011-03-31 08:35:02 -04:00
if ! exists('s:var_reset')
let s:var_reset = {}
endif
2011-03-29 11:01:47 -04:00
let buf = bufname("")
if a:0 == 0 && has_key(s:var_reset, a:var)
" Reset var to original value
2011-03-29 11:01:47 -04:00
call setbufvar(buf, a:var, s:var_reset[a:var])
elseif a:0 == 1
let new_value = a:0 == 1 ? a:1 : ''
" Store original value
2011-03-29 11:01:47 -04:00
let s:var_reset[a:var] = getbufvar(buf, a:var)
" Set new var value
2011-03-29 11:01:47 -04:00
call setbufvar(buf, a:var, new_value)
endif
endfunction " }}}
2011-03-28 12:04:35 -04:00
function! s:SetLines(lines, key) " {{{
try
" Try to join changes with previous undo block
undojoin
catch
endtry
2011-03-28 12:04:35 -04:00
for [line_num, line] in a:lines
2011-03-28 12:04:35 -04:00
call setline(line_num, line[a:key])
endfor
endfunction " }}}
function! s:GetChar() " {{{
let char = getchar()
if char == 27
" Escape key pressed
redraw
2011-03-29 02:25:44 -04:00
call s:Message('Cancelled')
return ''
endif
return nr2char(char)
endfunction " }}}
2011-03-31 03:27:28 -04:00
function! s:GetSearchChar(visualmode) " {{{
call s:Prompt('Search for character')
let char = s:GetChar()
" Check that we have an input char
if empty(char)
" Restore selection
if ! empty(a:visualmode)
silent exec 'normal! gv'
endif
return ''
endif
return char
2011-03-31 03:27:28 -04:00
endfunction " }}}
2011-03-27 18:08:06 -04:00
" }}}
" Core functions {{{
2011-04-01 02:48:33 -04:00
" Create key index {{{
function! s:CreateIndex(chars) " {{{
let index_to_key = {}
let key_to_index = {}
let idx = 0
for char in split(a:chars, '\zs')
let index_to_key[idx] = char
let key_to_index[char] = idx
let idx += 1
endfor
return [index_to_key, key_to_index]
endfunction "}}}
let [s:index_to_key, s:key_to_index] = s:CreateIndex(g:EasyMotion_keys)
" }}}
2011-03-27 18:08:06 -04:00
function! s:PromptUser(groups) "{{{
let single_group = len(a:groups) == 1
let targets_len = single_group ? len(a:groups[0]) : len(a:groups)
2011-04-01 02:48:33 -04:00
" If only one possible match, jump directly to it {{{
2011-03-27 18:08:06 -04:00
if single_group && targets_len == 1
redraw
return a:groups[0][0]
endif
" }}}
2011-03-28 03:03:49 -04:00
" Prepare marker lines {{{
let lines = {}
2011-03-27 18:08:06 -04:00
let hl_coords = []
2011-03-28 03:03:49 -04:00
let current_group = 0
2011-03-27 18:08:06 -04:00
for group in a:groups
let element = 0
2011-03-28 03:03:49 -04:00
for [line_num, col_num] in group
" Add original line and marker line
if ! has_key(lines, line_num)
let current_line = getline(line_num)
let lines[line_num] = { 'orig': current_line, 'marker': current_line }
2011-03-28 03:03:49 -04:00
endif
let marker_char = s:index_to_key[single_group ? element : current_group]
if strlen(lines[line_num]['marker']) > 0
" Replace hard tab with spaces
if match(lines[line_num]['marker'], '\%' . col_num . 'c\t') != -1
let marker_char .= repeat(' ', string(&tabstop) - strlen(marker_char))
endif
" Substitute marker character if line length > 0
let lines[line_num]['marker'] = substitute(lines[line_num]['marker'], '\%' . col_num . 'c.', marker_char, '')
else
" Set the line to the marker character if the line is empty
let lines[line_num]['marker'] = marker_char
endif
2011-03-27 18:08:06 -04:00
" Add highlighting coordinates
2011-03-28 03:03:49 -04:00
call add(hl_coords, '\%' . line_num . 'l\%' . col_num . 'c')
let element += 1
2011-03-27 18:08:06 -04:00
endfor
let current_group += 1
endfor
2011-03-28 03:04:22 -04:00
2011-03-28 03:03:49 -04:00
let lines_items = items(lines)
2011-03-27 18:08:06 -04:00
" }}}
2011-04-01 02:48:33 -04:00
" Highlight targets {{{
let target_hl_id = matchadd(g:EasyMotion_target_hl, join(hl_coords, '\|'), 1)
" }}}
2011-03-28 03:04:22 -04:00
2011-03-29 11:04:19 -04:00
try
" Set lines with markers
call s:SetLines(lines_items, 'marker')
2011-03-27 18:08:06 -04:00
2011-03-29 11:04:19 -04:00
redraw
2011-03-27 18:08:06 -04:00
2011-04-01 02:48:33 -04:00
" Get target/group character {{{
if single_group
call s:Prompt('Target character')
else
call s:Prompt('Group character')
endif
2011-03-27 18:08:06 -04:00
2011-04-01 02:48:33 -04:00
let char = s:GetChar()
" }}}
2011-03-29 11:04:19 -04:00
finally
" Restore original lines
call s:SetLines(lines_items, 'orig')
2011-03-27 18:08:06 -04:00
2011-04-01 02:48:33 -04:00
" Un-highlight targets {{{
if exists('target_hl_id')
call matchdelete(target_hl_id)
endif
" }}}
2011-03-27 18:08:06 -04:00
2011-03-29 11:04:19 -04:00
redraw
endtry
2011-03-27 18:08:06 -04:00
2011-04-01 02:48:33 -04:00
" Check if we have an input char {{{
if empty(char)
throw 'Cancelled'
endif
" }}}
" Check if the input char is valid {{{
if ! has_key(s:key_to_index, char) || s:key_to_index[char] >= targets_len
throw 'Invalid target'
endif
" }}}
2011-03-28 16:36:39 -04:00
if single_group
" Return target coordinates
2011-03-29 11:04:19 -04:00
return a:groups[0][s:key_to_index[char]]
2011-03-28 16:36:39 -04:00
else
" Prompt for target character
2011-03-29 11:04:19 -04:00
return s:PromptUser([a:groups[s:key_to_index[char]]])
2011-03-28 16:36:39 -04:00
endif
2011-03-27 18:08:06 -04:00
endfunction "}}}
function! s:EasyMotion(regexp, direction, visualmode, mode) " {{{
2011-03-27 18:08:06 -04:00
let orig_pos = [line('.'), col('.')]
let targets = []
try
2011-04-01 02:48:33 -04:00
" Reset properties {{{
call s:VarReset('&scrolloff', 0)
call s:VarReset('&modified', 0)
call s:VarReset('&modifiable', 1)
call s:VarReset('&readonly', 0)
" }}}
" Find motion targets {{{
let search_direction = (a:direction == 1 ? 'b' : '')
let search_stopline = line(a:direction == 1 ? 'w0' : 'w$')
2011-04-01 02:48:33 -04:00
while 1
let pos = searchpos(a:regexp, search_direction, search_stopline)
2011-03-27 18:08:06 -04:00
2011-04-01 02:48:33 -04:00
" Reached end of search range
if pos == [0, 0]
break
endif
2011-03-27 18:08:06 -04:00
2011-04-01 02:48:33 -04:00
" Skip folded lines
if foldclosed(pos[0]) != -1
continue
endif
2011-03-27 18:22:40 -04:00
2011-04-01 02:48:33 -04:00
call add(targets, pos)
endwhile
2011-03-28 04:14:51 -04:00
2011-04-01 02:48:33 -04:00
let targets_len = len(targets)
if targets_len == 0
throw 'No matches'
endif
" }}}
" Split targets into key groups {{{
2011-04-01 02:48:33 -04:00
let groups_len = len(s:index_to_key)
let groups = []
let i = 0
while i < targets_len
call add(groups, targets[i : i + groups_len - 1])
2011-03-27 18:08:06 -04:00
let i += groups_len
endwhile
" }}}
" Too many groups; only display the first ones {{{
if len(groups) > groups_len
2011-03-29 02:25:44 -04:00
call s:Message('Only displaying the first matches')
2011-03-27 18:08:06 -04:00
let groups = groups[0 : groups_len - 1]
endif
" }}}
2011-04-01 02:48:33 -04:00
" Shade inactive source {{{
if g:EasyMotion_do_shade
let shade_hl_pos = '\%' . orig_pos[0] . 'l\%'. orig_pos[1] .'c'
2011-03-27 18:08:06 -04:00
2011-04-01 02:48:33 -04:00
if a:direction == 1
" Backward
let shade_hl_re = '\%'. line('w0') .'l\_.*' . shade_hl_pos
else
" Forward
let shade_hl_re = shade_hl_pos . '\_.*\%'. line('w$') .'l'
endif
2011-03-27 18:08:06 -04:00
2011-04-01 02:48:33 -04:00
let shade_hl_id = matchadd(g:EasyMotion_shade_hl, shade_hl_re, 0)
endif
2011-04-01 02:48:33 -04:00
" }}}
2011-03-27 18:08:06 -04:00
" Prompt user for target group/character
2011-03-29 02:25:44 -04:00
let coords = s:PromptUser(groups)
2011-03-27 18:08:06 -04:00
2011-04-01 02:48:33 -04:00
" Update selection {{{
if ! empty(a:visualmode)
call cursor(orig_pos[0], orig_pos[1])
2011-03-28 16:36:39 -04:00
2011-04-01 02:48:33 -04:00
exec 'normal! ' . a:visualmode
endif
2011-04-01 02:48:33 -04:00
" }}}
" Handle operator-pending mode {{{
if a:mode == 'no'
" This mode requires that we eat one more
" character to the right if we're using
" a forward motion
if a:direction != 1
let coords[1] += 1
endif
endif
" }}}
2011-03-29 11:03:46 -04:00
" Update cursor position
2011-03-30 04:32:42 -04:00
call cursor(coords[0], coords[1])
2011-03-29 11:03:46 -04:00
2011-03-29 02:25:44 -04:00
call s:Message('Jumping to [' . coords[0] . ', ' . coords[1] . ']')
2011-03-29 11:04:19 -04:00
catch
redraw
2011-03-28 04:14:51 -04:00
" Show exception message
2011-03-29 02:25:44 -04:00
call s:Message(v:exception)
2011-04-01 02:48:33 -04:00
" Restore original cursor position/selection {{{
if ! empty(a:visualmode)
silent exec 'normal! gv'
else
call cursor(orig_pos[0], orig_pos[1])
endif
" }}}
finally
2011-04-01 02:48:33 -04:00
" Restore properties {{{
call s:VarReset('&scrolloff')
call s:VarReset('&modified')
call s:VarReset('&modifiable')
call s:VarReset('&readonly')
" }}}
" Remove shading {{{
if g:EasyMotion_do_shade && exists('shade_hl_id')
call matchdelete(shade_hl_id)
endif
" }}}
endtry
2011-03-27 18:08:06 -04:00
endfunction " }}}
" }}}