Toskan
Toskan

Reputation: 14961

Select first char up to first non camelCase or non upper case char or up to first snake case _ in vim

I used this map:

map ,w v/\([^ a-z0-9]\|[^ A-Z0-9]\)*<cr>h

the idea is to select

in the words

mysuperTest
MYSUPER_TEST
mysuper_test

to always select the part that says mysuper

but it doesnt work, not sure why

Upvotes: 1

Views: 279

Answers (1)

filbranden
filbranden

Reputation: 8908

I would use something like the below:

nnoremap ,w v/\C\%#.\([a-z]\+\<bar>[A-Z]\+\)\zs<cr>h

One point to notice is that in a mapping you need to use <bar> (or escape | with an extra backslash) since otherwise | is recognized as a command separator (see :help map-bar.)

Another one to notice is that you want the match to start at the first character outside the word (so you'll land at the end of the word with the h). The visual selection will expand to the start of the match in a search. I suggest using \zs to set the start of the match explicitly (see :help /\zs.)

Finally, beware of 'ignorecase' or 'smartcase' settings. Use \C to explicitly request a case-sensitive match (see :help /\C.)

I also like the idea of using a stronger anchor for the start of the match, so I'm using \%# to match the current cursor position (see :help /\%#), so you're always sure to match the current word only and not end up wandering through the buffer.

Putting it all together:

\C         Case-sensitive search
\%#        From cursor position
.          Skip first character
\(         Either one of:
  [a-z]\+  One or more lowercase letters
  \|       (\<bar>) Or:
  [A-Z]\+  One or more uppercase letters
\)         End group
\zs        Set match position here

I'm skipping the first character under the cursor, since in a CamelCase word, the first character won't match capitalization of the remainder of the word.

I kept your original idea of finding the first character after the word then using h to go back one to the left. But that might be a problem if, for example, the word is at the end of the line.

You can actually match the last character of the word instead with something like [a-z]\+\zs[a-z], which will set the start of the match on the last lowercase character. You can do this for both sides of the group (you can have more than one \zs in your pattern, last wins.) If you structure your match that way, you won't need the final h to go back.

I didn't handle numbers, I'll leave those as an exercise to the reader.

Finally, consider there are quite a few corner cases that can make such a mapping quite tricky to get right. Rather than coming up with your own, why not look at plug-ins which add support for handling CamelCase words that have been battle-tested and will cover use cases a lot more advanced than the simple expression you're using here?

There's the excellent vim-scripts/camelcasemotion by Ingo Karkat which sets up a ,w mapping to move to the start of the next CamelCase word, but also i,w to select the current one. You can use powerful combinations such as v3i,w to visually select the current and next two CamelCase words.

You might also check Tim Pope's tpope/vim-abolish which, among other features, defines a set of cr mappings to do coercion from camelCase to MixedCase, snake_case, UPPER_CASE, etc. (Not directly about selecting them, but still related and you might find it useful.)

Upvotes: 3

Related Questions