diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2022-08-24 01:26:08 -0600 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2022-08-24 01:26:08 -0600 |
commit | 0d83f507cef5f59d4b8afeed9633978394f2331a (patch) | |
tree | dd586ecd98dcfc16327d95ed1efc41b3f0796126 | |
parent | eabc175e8d7649f0d3308e8bec57f876107b8335 (diff) | |
download | fieldmarshal.vim-0d83f507cef5f59d4b8afeed9633978394f2331a.tar.gz fieldmarshal.vim-0d83f507cef5f59d4b8afeed9633978394f2331a.tar.bz2 fieldmarshal.vim-0d83f507cef5f59d4b8afeed9633978394f2331a.zip |
Implement subwords.vim
This plugin adds support for motions and text object based around
subwords. A "subword" is a constituent word of a larger "metaword"
in some case format. For examle "camel" is a "subword" of the "metaword"
"camelCase" or "camel_case".
The text objects added are
* `i-` the inner subword. For this would match something like
"[camel]_case" or "[camel]Case"
* `a-` around the subword. This distinction only matters for snake_case,
where the match would be "[camel_]case". For camelCase, this is the
same as `i-`.
* `i_`/`a_` like the above, but interpret ambiguous metawords as
snake_case. For example, the metaword ThisIs_AnExample is ambiguous
because it's both snake_case and camelCase. `i-`/`a-` interpet it as
camelCase, but `i_`/`a_` interpet it as snake_case.
ThisIs_AnExample
^
i- === [This]Is_AnExample
a- === [This]Is_AnExample
i_ === [ThisIs]_AnExample
a_ === [ThisIs_]AnExample
The motions added are
* `M--` (Meta + hyphen) Move forward one subword
* `M-_` (Meta + underscore) Move backward one subword.
-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! |