Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] Add feature to highlight characters to which the cursor can be moved directly #47

Merged
merged 11 commits into from Jun 26, 2019
60 changes: 60 additions & 0 deletions autoload/clever_f.vim
Expand Up @@ -13,10 +13,12 @@ let g:clever_f_hide_cursor_on_cmdline = get(g:, 'clever_f_hide_cursor_on_cmdlin
let g:clever_f_timeout_ms = get(g:, 'clever_f_timeout_ms', 0)
let g:clever_f_mark_char = get(g:, 'clever_f_mark_char', 1)
let g:clever_f_repeat_last_char_inputs = get(g:, 'clever_f_repeat_last_char_inputs', ["\<CR>"])
let g:clever_f_mark_direct = get(g:, 'clever_f_mark_direct', 0)

" below variables must be set before loading this script
let g:clever_f_mark_cursor_color = get(g:, 'clever_f_mark_cursor_color', 'Cursor')
let g:clever_f_mark_char_color = get(g:, 'clever_f_mark_char_color', 'CleverFDefaultLabel')
let g:clever_f_mark_direct_color = get(g:, 'clever_f_mark_direct_color', 'CleverFDefaultLabel')
let g:clever_f_clean_labels_eagerly = get(g:, 'clever_f_clean_labels_eagerly', 1)

" highlight labels
Expand All @@ -32,6 +34,9 @@ endif
if g:clever_f_mark_char
execute 'highlight link CleverFChar' g:clever_f_mark_char_color
endif
if g:clever_f_mark_direct
execute 'highlight link CleverFDirect' g:clever_f_mark_direct_color
endif

if g:clever_f_clean_labels_eagerly
augroup plugin-clever-f-permanent-finalizer
Expand Down Expand Up @@ -96,6 +101,52 @@ function! s:is_timedout() abort
return elapsed_ms > g:clever_f_timeout_ms
endfunction

" highlight characters to which the cursor can be moved directly
function! s:mark_direct(forward, count) abort
let line = getline('.')
let [_, l, c, _] = getpos('.')

if (a:forward && c == len(line)) || (!a:forward && c == 1)
" there is no matching characters
return []
endif

if g:clever_f_ignore_case
let line = tolower(line)
endif

let [i_start, i_end, i_step] = a:forward ? [c, len(line) - 1, 1]
\ : [c - 2, 0, -1]
let char_count = {}
let matches = []
for i in range(i_start, i_end, i_step)
let ch = line[i]
" only matches to ASCII
if ch !~ '^[\x00-\x7F]$' | continue | endif
let ch_lower = tolower(ch)

let char_count[ch] = get(char_count, ch, 0) + 1
if g:clever_f_smart_case && ch =~# '\u'
" uppercase characters are doubly counted
let char_count[ch_lower] = get(char_count, ch_lower, 0) + 1
endif

if char_count[ch] == a:count ||
\ (g:clever_f_smart_case && char_count[ch_lower] == a:count)
" NOTE: should not use `matchaddpos(group, [...position])`,
" because the maximum number of position is 8
let m = matchaddpos('CleverFDirect', [[l, i + 1]])
call add(matches, m)
endif
endfor
return matches
endfunction

" introduce public function for test
function! clever_f#_mark_direct(forward, count) abort
return s:mark_direct(a:forward, a:count)
endfunction

function! s:mark_char_in_current_line(map, char) abort
let regex = '\%' . line('.') . 'l' . s:generate_pattern(a:map, a:char)
call matchadd('CleverFChar', regex , 999)
Expand Down Expand Up @@ -148,6 +199,10 @@ function! clever_f#find_with(map) abort
endif
endif
try
if g:clever_f_mark_direct
let direct_markers = s:mark_direct(a:map =~# '\l', v:count1)
redraw
endif
if g:clever_f_show_prompt | echon 'clever-f: ' | endif
let s:previous_map[mode] = a:map
let s:first_move[mode] = 1
Expand Down Expand Up @@ -186,6 +241,11 @@ function! clever_f#find_with(map) abort
if g:clever_f_show_prompt | redraw! | endif
finally
if g:clever_f_mark_cursor | call matchdelete(cursor_marker) | endif
if g:clever_f_mark_direct
for m in direct_markers
call matchdelete(m)
endfor
endif
if g:clever_f_hide_cursor_on_cmdline
if s:ON_NVIM && mode ==# 'no'
" Do not preserve previous value at operator-pending mode on Neovim (#44)
Expand Down
21 changes: 21 additions & 0 deletions doc/clever_f.txt
Expand Up @@ -207,6 +207,27 @@ g:clever_f_repeat_last_char_inputs *g:clever_f_repeat_last_char_inputs*
>
let g:clever_f_repeat_last_char_inputs = ["\<CR>", "\<Tab>"]
<
g:clever_f_mark_direct *g:clever_f_mark_direct*

If the value is equivalent to 1, characters to which the cursor can be
moved directly are highlighted until you input a character. Highlighting
is enabled in normal and visual mode.
The default value is 0.

g:clever_f_mark_direct_color *g:clever_f_mark_direct_color*

If |g:clever_f_mark_direct| is enabled, |clever-f| highlights characters
using the highlight group which |g:clever_f_mark_direct_color| specifies.
If you want to change the highlight group |clever-f| uses, set
your favorite highlight group to this variable.
The default value is "CleverFDefaultLabel", which makes characters red and
bold. If you want to make an original label highlight, define your own
highlight group.(See |:highlight|)

Note:
|g:clever_f_mark_direct_color| must be set before |clever-f| is loaded.
(e.g. in your |vimrc|)


==============================================================================
SPECIAL THANKS *clever-f.vim-special-thanks*
Expand Down
184 changes: 184 additions & 0 deletions test/test.vimspec
Expand Up @@ -41,6 +41,8 @@ Describe default config
Assert Equals(g:clever_f_mark_char_color, 'CleverFDefaultLabel')
Assert Equals(g:clever_f_repeat_last_char_inputs, ["\<CR>"])
Assert Equals(g:clever_f_clean_labels_eagerly, 1)
Assert Equals(g:clever_f_mark_direct, 0)
Assert Equals(g:clever_f_mark_direct_color, 'CleverFDefaultLabel')
End
End

Expand Down Expand Up @@ -711,6 +713,23 @@ Describe <Esc>
normal Th
Assert Equals(col('.'), 7)
End

Context with g:clever_f_mark_direct
Before
let g:clever_f_mark_direct = 1
highlight link CleverFDirect CleverFDefaultLabel
End

After
let g:clever_f_mark_direct = 0
End

It should remove target highlights
normal! gg0
execute 'normal' "f\<Esc>"
Assert Equals(len(filter(getmatches(), 'v:val.group==#"CleverFDirect"')), 0)
End
End
End

Describe g:clever_f_smart_case
Expand Down Expand Up @@ -1093,4 +1112,169 @@ Describe selection=exclusive
End
End

Describe clever_f#_mark_direct()
function! GetHighlightedPositions()
let cols = sort(map(getmatches(), 'v:val.pos1[1]'), 'n')
let chars = []
for c in range(1, 19)
if len(cols) > 0 && cols[0] == c
let ch = '_'
call remove(cols, 0)
else
let ch = ' '
endif
call add(chars, ch)
endfor
return join(chars, '')
endfunction

Before
new
call clever_f#reset()
highlight link CleverFDirect CleverFDefaultLabel
call AddLine('ビムかわいいよzビムx')
call AddLine('pOge huga Hiyo pOyo')
let old_across_no_line = g:clever_f_across_no_line
let g:clever_f_across_no_line = 0
End

After
close!
let g:clever_f_mark_direct = 0
let g:clever_f_across_no_line = old_across_no_line
End

It should highlight characters to which the cursor can be moved directly
normal! gg0
" #: cursor position, _: highlighted char
"
" #Oge huga Hiyo pOyo
let s = ' ______ _ ____ _ '
call clever_f#_mark_direct(1, 1)
Assert Equals(GetHighlightedPositions(), s)
End

It should highlight backward characters
normal! gg$
" pOge huga Hiyo pOy#
let s = ' _ ____ __ _____ '
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very easy to understand. Thank you.

call clever_f#_mark_direct(0, 1)
Assert Equals(GetHighlightedPositions(), s)
End

It should highlight characters to which the cursor can be moved by one hop
normal! gg0
" #Oge huga Hiyo pOyo
let s = ' _ _ ___'
call clever_f#_mark_direct(1, 2)
Assert Equals(GetHighlightedPositions(), s)
End

It should not highlight multibyte characters
normal! 2gg0
" #ムかわいいよzビムx
" _ _
call clever_f#_mark_direct(1, 1)
let cols = [22, 29]
Assert Equals(sort(map(getmatches(), 'v:val.pos1[1]'), 'n'), cols)
End

Context with g:clever_f_smart_case
Before
let g:clever_f_smart_case = 1
End

After
let g:clever_f_smart_case = 0
End

It should highlight characters to which the cursor can be moved directly
normal! gg0
" #Oge huga Hiyo pOyo
let s = ' ______ _ ___ _ '
call clever_f#_mark_direct(1, 1)
Assert Equals(GetHighlightedPositions(), s)
End

It should highlight backward characters
normal! gg$
" pOge huga Hiyo pOy#
let s = ' _ ___ __ ____ '
call clever_f#_mark_direct(0, 1)
Assert Equals(GetHighlightedPositions(), s)
End

It should highlight characters to which the cursor can be moved by one hop
normal! gg0
" #Oge huga Hiyo pOyo
let s = ' _ __ _ __ '
call clever_f#_mark_direct(1, 2)
Assert Equals(GetHighlightedPositions(), s)
End
End

Context with g:clever_f_ignore_case
Before
let g:clever_f_ignore_case = 1
End

After
let g:clever_f_ignore_case = 0
End

It should highlight characters to which the cursor can be moved directly
normal! gg0
" #Oge huga Hiyo pOyo
let s = ' ______ _ __ _ '
call clever_f#_mark_direct(1, 1)
Assert Equals(GetHighlightedPositions(), s)
End

It should highlight backward characters
normal! gg$
" pOge huga Hiyo pOy#
let s = ' _ ___ __ ____ '
call clever_f#_mark_direct(0, 1)
Assert Equals(GetHighlightedPositions(), s)
End

It should highlight characters to which the cursor can be moved by one hop
normal! gg0
" #Oge huga Hiyo pOyo
let s = ' _ __ _ _ '
call clever_f#_mark_direct(1, 2)
Assert Equals(GetHighlightedPositions(), s)
End
End
End

Describe g:clever_f_mark_direct
Before
new
let g:clever_f_mark_direct = 1
call clever_f#reset()
highlight link CleverFDirect CleverFDefaultLabel
call AddLine('pOge huga Hiyo poyo')
let old_across_no_line = g:clever_f_across_no_line
let g:clever_f_across_no_line = 0
End

After
close!
let g:clever_f_mark_direct = 0
let g:clever_f_across_no_line = old_across_no_line
End

It should remove target highlights
normal! gg0
normal fh
Assert Equals(len(filter(getmatches(), 'v:val.group ==# "CleverFDirect"')), 0)
End

It should finish with no error
normal! gg$
normal fp
Assert Equals(len(filter(getmatches(), 'v:val.group ==# "CleverFDirect"')), 0)
End
End
" vim:foldmethod=marker