Merge branch 'experiment-preserve-syntax'

This commit is contained in:
haya14busa 2016-01-21 16:17:47 +09:00
commit 65ee835a4e
3 changed files with 169 additions and 76 deletions

View File

@ -11,7 +11,8 @@ function! EasyMotion#overwin#move(pattern) abort
\ }, \ },
\ 'jump_first_target_keys': \ 'jump_first_target_keys':
\ (g:EasyMotion_enter_jump_first ? ["\<CR>"] : []) + \ (g:EasyMotion_enter_jump_first ? ["\<CR>"] : []) +
\ (g:EasyMotion_space_jump_first ? ["\<Space>"] : []) \ (g:EasyMotion_space_jump_first ? ["\<Space>"] : []),
\ 'do_shade': g:EasyMotion_do_shade,
\ }) \ })
endfunction endfunction

View File

@ -20,6 +20,21 @@ let s:TRUE = !0
let s:FALSE = 0 let s:FALSE = 0
let s:DIRECTION = {'forward': 0, 'backward': 1} let s:DIRECTION = {'forward': 0, 'backward': 1}
" Check Vim version
function! s:has_patch(major, minor, patch) abort
let l:version = (a:major * 100 + a:minor)
return has('patch-' . a:major . '.' . a:minor . '.' . a:patch) ||
\ (v:version > l:version) ||
\ (v:version == l:version && 'patch' . a:patch)
endfunction
" matchadd('Conceal', {pattern}, {priority}, -1, {'conceal': {char}}}) can
" highlight pattern and conceal target correctly even if the target is keyword
" characters.
" - http://ftp.vim.org/vim/patches/7.4/7.4.792
" - https://groups.google.com/forum/#!searchin/vim_dev/matchadd$20conceal/vim_dev/8bKa98GhHdk/VOzIBhd1m8YJ
let s:can_preserve_syntax = s:has_patch(7, 4, 792)
" s:move() moves cursor over/accross window with Hit-A-Hint feature like " s:move() moves cursor over/accross window with Hit-A-Hint feature like
" vim-easymotion " vim-easymotion
" @param {dict} config " @param {dict} config
@ -54,6 +69,7 @@ let s:overwin = {
\ 'target': 'HitAHintTarget', \ 'target': 'HitAHintTarget',
\ }, \ },
\ 'jump_first_target_keys': [], \ 'jump_first_target_keys': [],
\ 'do_shade': s:TRUE,
\ } \ }
\ } \ }
@ -106,7 +122,27 @@ function! s:overwin.select_winpos(winnr2poses, keys) abort
if self.config.auto_land && len(wposes) is# 1 if self.config.auto_land && len(wposes) is# 1
return wposes[0] return wposes[0]
endif endif
return self.choose_prompt(s:Hint.create(wposes, a:keys)) call self.set_options()
try
return self.choose_prompt(s:Hint.create(wposes, a:keys))
finally
call self.restore_options()
endtry
endfunction
function! s:overwin.set_options() abort
" s:move_to_win() takes long time if 'foldmethod' == 'syntax' or 'expr'
let self.save_foldmethod = {}
for winnr in range(1, winnr('$'))
let self.save_foldmethod[winnr] = getwinvar(winnr, '&foldmethod')
call setwinvar(winnr, '&foldmethod', 'manual')
endfor
endfunction
function! s:overwin.restore_options() abort
for winnr in range(1, winnr('$'))
call setwinvar(winnr, '&foldmethod', self.save_foldmethod[winnr])
endfor
endfunction endfunction
" s:wpos_to_hint() returns dict whose key is position with window and whose " s:wpos_to_hint() returns dict whose key is position with window and whose
@ -186,7 +222,7 @@ function! s:overwin.choose_prompt(hint_dict) abort
let c = toupper(c) let c = toupper(c)
endif endif
catch catch
echo v:exception echo v:throwpoint . ':' . v:exception
return -1 return -1
finally finally
call hinter.after() call hinter.after()
@ -230,21 +266,30 @@ let s:Hinter = {
function! s:Hinter.new(hint_dict, config) abort function! s:Hinter.new(hint_dict, config) abort
let s = deepcopy(self) let s = deepcopy(self)
let s.config = a:config let s.config = a:config
let win2pos2hint = s:create_win2pos2hint(a:hint_dict) call s.init(a:hint_dict)
let s.winnrs = map(keys(win2pos2hint), 'str2nr(v:val)')
let s.win2pos2hint = win2pos2hint
let s.w2l2c2h = s:win2pos2hint_to_w2l2c2h(win2pos2hint)
call s._save_lines()
return s return s
endfunction endfunction
function! s:Hinter.init(hint_dict) abort
let win2pos2hint = s:create_win2pos2hint(a:hint_dict)
let self.winnrs = sort(map(keys(win2pos2hint), 'str2nr(v:val)'))
let self.win2pos2hint = win2pos2hint
let self.w2l2c2h = s:win2pos2hint_to_w2l2c2h(win2pos2hint)
let self.hl_target_ids = {}
for winnr in self.winnrs
let self.hl_target_ids[winnr] = []
endfor
call self._save_lines()
endfunction
function! s:Hinter.before() abort function! s:Hinter.before() abort
call self.modify_env() let self.highlight_id_cursor = matchadd('Cursor', '\%#', 101)
call self.save_options()
call self.disable_conceal_in_other_win() call self.disable_conceal_in_other_win()
endfunction endfunction
function! s:Hinter.after() abort function! s:Hinter.after() abort
call self.restore_lines() call matchdelete(self.highlight_id_cursor)
call self.restore_env() call self.restore_env()
call self.restore_conceal_in_other_win() call self.restore_conceal_in_other_win()
endfunction endfunction
@ -265,83 +310,94 @@ function! s:Hinter._save_lines() abort
endtry endtry
endfunction endfunction
function! s:Hinter.restore_lines() abort function! s:Hinter.restore_lines_for_win(winnr) abort
let nr = winnr() let lnum2line = self.save_lines[a:winnr]
try for [lnum, line] in items(lnum2line)
for [winnr, lnum2line] in items(self.save_lines) call s:setline(lnum, line)
call s:move_to_win(winnr) endfor
for [lnum, line] in items(lnum2line)
call s:setline(lnum, line)
endfor
endfor
finally
call s:move_to_win(nr)
endtry
endfunction endfunction
function! s:Hinter.modify_env() abort function! s:Hinter.save_options() abort
let nr = winnr() for winnr in self.winnrs
try let self.save_syntax[winnr] = getwinvar(winnr, '&syntax')
let self.highlight_id_cursor = matchadd('Cursor', '\%#', 1000001) let self.save_conceallevel[winnr] = getwinvar(winnr, '&conceallevel')
for winnr in self.winnrs let self.save_concealcursor[winnr] = getwinvar(winnr, '&concealcursor')
call s:move_to_win(winnr) let self.save_modified[winnr] = getwinvar(winnr, '&modified')
let self.save_conceal = s:PHighlight.get('Conceal') let self.save_modifiable[winnr] = getwinvar(winnr, '&modifiable')
let self.save_syntax[winnr] = &syntax let self.save_readonly[winnr] = getwinvar(winnr, '&readonly')
let self.save_conceallevel[winnr] = &l:conceallevel endfor
let self.save_concealcursor[winnr] = &l:concealcursor endfunction
let self.save_modified[winnr] = &l:modified
let self.save_modifiable[winnr] = &l:modifiable
let self.save_readonly[winnr] = &l:readonly
let self.save_undo[winnr] = s:undo_lock.save() function! s:Hinter.restore_options() abort
for winnr in self.winnrs
call setwinvar(winnr, '&conceallevel', self.save_conceallevel[winnr])
call setwinvar(winnr, '&concealcursor', self.save_concealcursor[winnr])
call setwinvar(winnr, '&modified', self.save_modified[winnr])
call setwinvar(winnr, '&modifiable', self.save_modifiable[winnr])
call setwinvar(winnr, '&readonly', self.save_readonly[winnr])
endfor
endfunction
setlocal modifiable function! s:Hinter.modify_env_for_win(winnr) abort
setlocal noreadonly let self.save_conceal = s:PHighlight.get('Conceal')
let self.save_undo[a:winnr] = s:undo_lock.save()
ownsyntax overwin setlocal modifiable
setlocal noreadonly
if !s:can_preserve_syntax
ownsyntax overwin
endif
setlocal conceallevel=2
setlocal concealcursor=ncv
let self.highlight_ids[a:winnr] = get(self.highlight_ids, a:winnr, [])
if self.config.do_shade
if !s:can_preserve_syntax
syntax clear syntax clear
setlocal conceallevel=2 endif
setlocal concealcursor=ncv let self.highlight_ids[a:winnr] += [matchadd(self.config.highlight.shade, '\_.*', 100)]
execute 'highlight! link Conceal' self.config.highlight.target endif
let self.highlight_ids[winnr] = get(self.highlight_ids, winnr, []) " XXX: other plugins specific handling
let self.highlight_ids[winnr] += [matchadd(self.config.highlight.shade, '\_.*', 100)] if getbufvar('%', 'indentLine_enabled', 0)
endfor silent! syntax clear IndentLine
catch endif
call s:throw(v:throwpoint . ' ' . v:exception)
finally
call s:move_to_win(nr)
endtry
endfunction endfunction
function! s:Hinter.restore_env() abort function! s:Hinter.restore_env() abort
call s:PHighlight.set('Conceal', self.save_conceal)
let nr = winnr() let nr = winnr()
try try
call matchdelete(self.highlight_id_cursor)
for winnr in self.winnrs for winnr in self.winnrs
call s:move_to_win(winnr) call s:move_to_win(winnr)
" Clear syntax defined by Hit-A-Hint motion before restoring syntax. call self.restore_lines_for_win(winnr)
syntax clear HitAHintTarget call self.remove_hints(winnr)
let &syntax = self.save_syntax[winnr]
call s:PHighlight.set('Conceal', self.save_conceal) if !s:can_preserve_syntax && self.config.do_shade
let &l:conceallevel = self.save_conceallevel[winnr] let &syntax = self.save_syntax[winnr]
let &l:concealcursor = self.save_concealcursor[winnr] endif
call self.save_undo[winnr].restore() call self.save_undo[winnr].restore()
let &l:modified = self.save_modified[winnr]
let &l:modifiable = self.save_modifiable[winnr]
let &l:readonly = self.save_readonly[winnr]
for id in self.highlight_ids[winnr] for id in self.highlight_ids[winnr]
call matchdelete(id) call matchdelete(id)
endfor endfor
" XXX: other plugins specific handling
if getbufvar('%', 'indentLine_enabled', 0) && exists(':IndentLinesEnable') is# 2
call setbufvar('%', 'indentLine_enabled', 0)
:IndentLinesEnable
endif
endfor endfor
catch catch
call s:throw(v:throwpoint . ' ' . v:exception) call s:throw(v:throwpoint . ' ' . v:exception)
finally finally
call s:move_to_win(nr) call s:move_to_win(nr)
endtry endtry
call self.restore_options()
endfunction endfunction
let s:undo_lock = {} let s:undo_lock = {}
@ -440,12 +496,24 @@ function! s:Hinter.show_hint() abort
endfunction endfunction
function! s:Hinter._show_hint_for_win(winnr) abort function! s:Hinter._show_hint_for_win(winnr) abort
call self.modify_env_for_win(a:winnr)
let hints = []
for [lnum, col2hint] in items(self.w2l2c2h[a:winnr]) for [lnum, col2hint] in items(self.w2l2c2h[a:winnr])
call self._show_hint_for_line(a:winnr, lnum, col2hint) let hints += self._show_hint_for_line(a:winnr, lnum, col2hint)
endfor
" Restore syntax and show hints after replacing all lines for performance.
if !self.config.do_shade
let &l:syntax = self.save_syntax[a:winnr]
endif
execute 'highlight! link Conceal' self.config.highlight.target
for [lnum, cnum, char] in hints
call self.show_hint_pos(lnum, cnum, char, a:winnr)
endfor endfor
endfunction endfunction
function! s:Hinter._show_hint_for_line(winnr, lnum, col2hint) abort function! s:Hinter._show_hint_for_line(winnr, lnum, col2hint) abort
let hints = [] " [lnum, cnum, char]
let line = self.save_lines[a:winnr][a:lnum] let line = self.save_lines[a:winnr][a:lnum]
let col_offset = 0 let col_offset = 0
let prev_cnum = -1 let prev_cnum = -1
@ -467,14 +535,15 @@ function! s:Hinter._show_hint_for_line(winnr, lnum, col2hint) abort
endif endif
let col_offset += offset let col_offset += offset
call s:show_hint_pos(a:lnum, col_num, hint[0]) let hints = [[a:lnum, col_num, hint[0]]] + hints
if len(hint) > 1 if len(hint) > 1
call s:show_hint_pos(a:lnum, col_num + 1, hint[1]) let hints = [[a:lnum, col_num + 1, hint[1]]] + hints
endif endif
let prev_cnum = cnum let prev_cnum = cnum
endfor endfor
call s:setline(a:lnum, line) call s:setline(a:lnum, line)
return hints
endfunction endfunction
" ._replace_line_for_hint() replaces line to show hints. " ._replace_line_for_hint() replaces line to show hints.
@ -489,11 +558,13 @@ endfunction
function! s:Hinter._replace_line_for_hint(lnum, col_num, line, hint) abort function! s:Hinter._replace_line_for_hint(lnum, col_num, line, hint) abort
let line = a:line let line = a:line
let col_num = a:col_num let col_num = a:col_num
let do_replace_target = !(self.config.do_shade || s:can_preserve_syntax)
let target = matchstr(line, '\%' . col_num .'c.') let target = matchstr(line, '\%' . col_num .'c.')
" Append one space for empty line or match at end of line " Append one space for empty line or match at end of line
if target is# '' if target is# ''
let hintwidth = strdisplaywidth(join(a:hint[:1], '')) let hintwidth = strdisplaywidth(join(a:hint[:1], ''))
let line .= repeat(' ', hintwidth) let char = do_replace_target ? ' ' : '.'
let line .= repeat(char, hintwidth)
return [line, hintwidth, 0] return [line, hintwidth, 0]
endif endif
@ -503,12 +574,20 @@ function! s:Hinter._replace_line_for_hint(lnum, col_num, line, hint) abort
elseif strdisplaywidth(target) > 1 elseif strdisplaywidth(target) > 1
let line = self._replace_text_to_space(line, a:lnum, col_num, strdisplaywidth(target)) let line = self._replace_text_to_space(line, a:lnum, col_num, strdisplaywidth(target))
let offset = strdisplaywidth(target) - len(target) let offset = strdisplaywidth(target) - len(target)
else
if do_replace_target
" The priority of :syn-cchar is always under the priority of keywords.
" So, Hit-A-Hint replaces targets character with '.'.
let space = '.'
let line = substitute(line, '\%' . col_num . 'c.', space, '')
let offset = len(space) - len(target)
endif
endif endif
let next_offset = 0 let next_offset = 0
if len(a:hint) > 1 if len(a:hint) > 1 && target isnot# "\t"
" pass [] as hint to stop recursion. " pass [' '] as hint to stop recursion.
let [line, next_offset, _] = self._replace_line_for_hint(a:lnum, col_num + offset + 1, line, []) let [line, next_offset, _] = self._replace_line_for_hint(a:lnum, col_num + offset + 1, line, [' '])
endif endif
return [line, offset, next_offset] return [line, offset, next_offset]
endfunction endfunction
@ -526,6 +605,26 @@ function! s:Hinter._replace_text_to_space(line, lnum, col_num, len) abort
return line return line
endfunction endfunction
function! s:Hinter.show_hint_pos(lnum, cnum, char, winnr) abort
let p = '\%'. a:lnum . 'l\%'. a:cnum . 'c.'
if s:can_preserve_syntax
let self.hl_target_ids[a:winnr] += [matchadd('Conceal', p, 101, -1, {'conceal': a:char})]
else
exec "syntax match HitAHintTarget '". p . "' contains=NONE containedin=.* conceal cchar=". a:char
endif
endfunction
function! s:Hinter.remove_hints(winnr) abort
if s:can_preserve_syntax
for id in self.hl_target_ids[a:winnr]
call matchdelete(id)
endfor
else
" Clear syntax defined by Hit-A-Hint motion before restoring syntax.
syntax clear HitAHintTarget
endif
endfunction
" @param {number} col_num col_num is 1 origin like col() " @param {number} col_num col_num is 1 origin like col()
function! s:tab2spacelen(line, col_num) abort function! s:tab2spacelen(line, col_num) abort
let before_line = a:col_num > 2 ? a:line[: a:col_num - 2] let before_line = a:col_num > 2 ? a:line[: a:col_num - 2]
@ -665,11 +764,6 @@ function! s:wincall(func, arglist, ...) abort
return r return r
endfunction endfunction
function! s:show_hint_pos(lnum, cnum, char) abort
let p = '\%'. a:lnum . 'l\%'. a:cnum . 'c.'
exec "syntax match HitAHintTarget '". p . "' conceal cchar=". a:char
endfunction
" deepextend (nest: 1) " deepextend (nest: 1)
function! s:deepextend(expr1, expr2) abort function! s:deepextend(expr1, expr2) abort
let expr2 = copy(a:expr2) let expr2 = copy(a:expr2)

View File

@ -332,9 +332,7 @@ For ovrewin n-character find motions~
Since archtecture of overwin motions is different from other easymotion Since archtecture of overwin motions is different from other easymotion
motions, there are some limitations. motions, there are some limitations.
1. |EasyMotion_do_shade| by default and currently you cannot turned off 1. Highlight for target is always EasyMotionTarget (|EasyMotion_highlight|)
this option.
2. Highlight for target is always EasyMotionTarget (|EasyMotion_highlight|)
even for two key targets. even for two key targets.
Jump To Anywhere ~ Jump To Anywhere ~