diff options
-rw-r--r-- | plugin/subwords.vim | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/plugin/subwords.vim b/plugin/subwords.vim new file mode 100644 index 0000000..e31796c --- /dev/null +++ b/plugin/subwords.vim @@ -0,0 +1,174 @@ +" subwords.vim. Motions and text objects for dealing with subwords. +" +" A subword is a word within an identifier, like a word within a camelCase +" identifer. +" + +" Text object _. This references a "subword" 'a_' is around, which in the case +" of snake_case identifier will include the underscores (_). The 'i_' references +" the ci_ + +onoremap <silent> i- :<c-u>exec "norm " . <sid>v_subword(v:true, v:false)<cr> +vnoremap <silent> i- :<c-u>exec "norm " . <sid>v_subword(v:true, v:false)<cr> +onoremap <silent> a- :<c-u>exec "norm " . <sid>v_subword(v:true, v:true)<cr> +vnoremap <silent> a- :<c-u>exec "norm " . <sid>v_subword(v:true, v:true)<cr> + +" These mappings are the same as above, except prefer_camel is turned off, so +" snake case is used in the case of a conflict. +onoremap <silent> i_ :<c-u>exec "norm " . <sid>v_subword(v:false, v:false)<cr> +vnoremap <silent> i_ :<c-u>exec "norm " . <sid>v_subword(v:false, v:false)<cr> +onoremap <silent> a_ :<c-u>exec "norm " . <sid>v_subword(v:false, v:true)<cr> +vnoremap <silent> a_ :<c-u>exec "norm " . <sid>v_subword(v:false, v:true)<cr> + +" Movement keys for subwords. These all have prefer_camel set to true, the idea +" being it's pretty easy to navigate underscores with f_ and t_, but more +" difficult to navigate upper case letters. +noremap <silent> <M--> :<c-u>silent! call <SID>next_subword(v:false, v:true)<cr> +noremap <silent> <M-_> :<c-u>silent! call <SID>next_subword(v:false, v:false)<cr> +vnoremap <silent> <M--> v:<c-u>silent! call <SID>next_subword(v:true, v:true)<cr>m'gv`` +vnoremap <silent> <M-_> v:<c-u>silent! call <SID>next_subword(v:true, v:false)<cr>m'gv`` + +" Return the type of meta-word (i.e. camelCase, snake_case). If +" a:prefer_camel is set, then a word like ThisIs_A_MixOfCamel_And_Snake will +" some_snake_case SomeCamelCase SOME_CONSTANT_CASE +" return 'camel', otherwise it'll return 'snake'. +function! s:detect_word_type(prefer_camel, word) abort + let is_camel = 0 + if a:word =~ '[a-z][A-Z]' + let is_camel = 1 + + if a:prefer_camel + " The word contains a camelCase boundary. + return 'camel' + endif + endif + + if a:word =~ '_' + " The word contains a sake_case boundary. + return 'snake' + endif + + if is_camel + return 'camel' + endif + + " There is not discernible type, it's probably just a single word. + return 'word' +endfunction + +function! s:next_subword(vis, forward) + let i = 0 + while i < v:count1 + call search( + \ '\([a-z]\zs[A-Z]\ze\)\|\(\W\|_\)\zs\w\ze', (a:forward ? '' : 'b')) + let i += 1 + endwhile +endfunction + +" Highlight an inner subword. +function! s:v_subword(prefer_camel, around) + + " Detect the type of the word. + let word = expand("<cword>") + let t = s:detect_word_type(a:prefer_camel, word) + + let expr = '' + + if t == 'camel' + let expr = 'v' + + let line = getline('.') + let c = col('.') - 1 + let i = 0 + + while line[c] =~ '[a-z]' && c >= 0 + let c -= 1 + let i += 1 + endwhile + + " camelCase + + if c >= 0 && ! (line[c] =~ '[A-Z_]') + " If we are at the beginning of the meta word, the don't go back too far. + let i -= 1 + endif + + if i > 0 + let expr .= printf("%dh", i) + endif + + let expr .= 'o' + + let c = col('.') - 1 + let i = 0 + + if line[c] =~ '[A-Z_]' + " Actually on the starting capital letter, include it, but start counting + " from the next character. + let i += 1 + let c += 1 + endif + + while c < len(line) && line[c] =~ '[a-z]' + let c += 1 + let i += 1 + endwhile + + if i > 1 + let expr .= printf("%dl", i - 1) + endif + + elseif t == "snake" + let expr = 'v' + + let line = getline('.') + let c = col('.') - 1 + let i = 0 + + while c >= 0 && !( line[c] =~ '\W' ) && line[c] != '_' + let c -= 1 + let i += 1 + endwhile + + let lhs_under = c >= 0 && line[c] == '_' + + let i -= 1 + let c += 1 + + if i > 0 + let expr .= printf('%dho', i) + endif + + let c = col('.') - 1 + let i = 0 + + while c < len(line) && !(line[c] =~ '\W') && line[c] != '_' + let c += 1 + let i += 1 + endwhile + + let rhs_under = c < len(line) && line[c] == '_' + + let i -= 1 + let c -= 1 + + if i > 0 + let expr .= printf('%dl', i) + endif + + if a:around + if rhs_under + let expr .= 'l' + elseif lhs_under + let expr .= 'oho' + endif + endif + + + elseif t == "word" + " Just a word? Easy peasy. + let expr = 'viw' + endif + + return expr +endfunction! |