aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--plugin/subwords.vim174
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!