" 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 (inner-sub-word) :exec "norm " . v_subword(v:true, v:false) vnoremap (inner-sub-word) :exec "norm " . v_subword(v:true, v:false) onoremap (around-sub-word) :exec "norm " . v_subword(v:true, v:true) vnoremap (around-sub-word) :exec "norm " . v_subword(v:true, v:true) if ! exists('g:subwords_include_bindings') let g:subwords_include_bindings = 1 endif " 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 (inner-sub-word-prefer-snake) :exec "norm " . v_subword(v:false, v:false) vnoremap (inner-sub-word-prefer-snake) :exec "norm " . v_subword(v:false, v:false) onoremap (around-sub-word-prefer-snake) :exec "norm " . v_subword(v:false, v:true) vnoremap (around-sub-word-prefer-snake) :exec "norm " . v_subword(v:false, v:true) " 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 (next-subword) :silent! call next_subword(v:false, v:true) noremap (prev-subword) :silent! call next_subword(v:false, v:false) vnoremap (next-subword) visualmode() . ":\silent! call \next_subword(visualmode(), v:true)\m'gv``" vnoremap (prev-subword) visualmode() . ":\silent! call \next_subword(visualmode(), v:false)m'gv``" function! s:clear_subword_mark() let s:subword_motion = "" return '' endfunction function! s:subword_repeat(char) if s:subword_motion == '' return a:char endif let mot = (s:subword_motion == 'next') != (a:char == ',') let s:subword_nosave = 1 if mot return "\(next-subword)" else return "\(prev-subword)" endif endfunction noremap (subwords-replace-;) subword_repeat(';') noremap (subwords-replace-,) subword_repeat(',') vnoremap (subwords-replace-;) subword_repeat(';') vnoremap (subwords-replace-,) subword_repeat(',') noremap (subwords-replace-t) clear_subword_mark() . "t" noremap (subwords-replace-f) clear_subword_mark() . "f" noremap (subwords-replace-T) clear_subword_mark() . "T" noremap (subwords-replace-F) clear_subword_mark() . "F" let s:subword_motion = "" let s:subword_nosave = 0 " 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_cae 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) if ! s:subword_nosave if a:forward let s:subword_motion = 'next' else let s:subword_motion = 'prev' endif endif 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 let s:subword_nosave = 0 endfunction " Highlight an inner subword. function! s:v_subword(prefer_camel, around) " Detect the type of the word. let word = expand("") 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! if g:subwords_include_bindings onoremap i_ (inner-sub-word-prefer-snake) vnoremap i_ (inner-sub-word-prefer-snake) onoremap a_ (around-sub-word-prefer-snaked) vnoremap a_ (around-sub-word-prefer-snaked) onoremap i- (inner-sub-word) vnoremap i- (inner-sub-word) onoremap a- (around-sub-word) vnoremap a- (around-sub-word) noremap + (next-subword) vnoremap + (next-subword) noremap - (prev-subword) vnoremap - (prev-subword) noremap ; (subwords-replace-;) noremap , (subwords-replace-,) vnoremap ; (subwords-replace-;) vnoremap , (subwords-replace-,) noremap t (subwords-replace-t) noremap T (subwords-replace-T) noremap f (subwords-replace-f) noremap F (subwords-replace-F) endif