This answer builds on dedowsdi's answer, but implements something closer to your specification:
- Turns the word under the cursor into uppercase (cursor doesn't have to be at the start of the word.)
- Doesn't change the cursor position while doing that.
- Uses
CTRL-U
for mapping.
- Acts on keyword characters (as you mentioned
<cword>
and not <cWORD>
, and I assume you did so intentionally.)
If the cursor is on whitespace, on a non-keyword character, or at the end of the line, it will act on the word before the cursor. This is useful to turn the last typed word to uppercase by simply typing it and pressing CTRL-U
, and you'll be able to resume the command-line from there.
In particular, I agree with dedowsdi's choice of Vim features to implement this:
CTRL-\ e
is the most flexible way to write mappings for the command-line mode. Using q:
or CTRL-F
make mappings easier to write, by acting on a buffer with normal mode commands, but it doesn't seem like it's possible to dismiss that window upon returning to the command-line.
matchlist()
with a suitable regex, and in particular using the \%Nc
family for matching on a column is the way to go to find the word under the cursor. (I was thinking of writing a split()
and a for
loop and quickly got stuck there.)
Vimscript code and mapping
The code is below. I used a public function suitable for .vimrc
, but it should be easy to adapt it to use a s:
function and refer to it by <sid>
as well.
function! CmdUpcaseWord()
" Split cmdline into 3 parts, where the middle
" one is the word under the cursor (or before
" the cursor, if the cursor is at the end, on
" whitespace or on a non-keyword character.)
let re = '\v(.*<)%<'.(getcmdpos()+1).'c(\w+)(.*)'
let matches = matchlist(getcmdline(), re)
if len(matches) < 4
return getcmdline()
endif
let [pre, word, post] = matches[1:3]
return pre.toupper(word).post
endfunction
cnoremap <c-u> <c-\>eCmdUpcaseWord()<cr>
The regex
Let's break down the regex.
\v
: Use the "very magic" flavor of regexes, so that it works independent of set nomagic
and fewer backslashes are needed.
(.*<)
: Find the longest sequence ending on a "start of word" boundary. Under "very magic", <
matches the beginning of a word (none of the symbols need to be backslashed.) And as *
is greedy, the longest match will be used. Capture it in the first group.
%<NNc
: Matches before column *NN
, in this case, up to the column where the cursor is. Considering we want to accept a match ending at the column where the cursor is, we use getcmdpos()+1
to make it equivalent to a <=
match.
(\w+)
: Followed by a word, captured in the middle group.
(.*)
: Finally, capture the rest of the line, captured in the last group.
The end result of using a greedy regex at the start, a match for word boundary, a constraint for the column of the cursor and following it right with word characters accomplishes what was specified.
If you would like to match a WORD (as in <cWORD>
) instead, match a sequence of non-blank characters, you can use this regex instead:
let re = '\v(.*\s)?%<'.(getcmdpos()+1).'c(\S+)(.*)'
I tested this with the usual corner cases, and it seems to work as expected.
Keybinding
I agree with the point made by D. Ben Knoble in his answer, CTRL-U
is a useful keystroke (for "kill line") in command-line mode, so I'd recommend using a different keystroke instead.
Also, as you want to start adding more bindings to the command-line mode, you'll end up running out of CTRL-key sequences quickly.
My recommendation would be to use CTRL-G
, which is unused, and then use a second keystroke after CTRL-G
, so you have a whole new namespace of mappings to exploit.
Also, since the g
mappings in normal mode work similarly as a namespace (in fact, there's gU
for uppercase and gu
for lowercase), you can use something similar for CTRL-G
in command-line mode.
cnoremap <c-g>U <c-\>eCmdUpcaseWord()<cr>
cnoremap <c-g>u <c-\>eCmdLocaseWord()<cr>
" etc.
Command-line window
Finally, in another point made by D. Ben Knoble, I'd also recommend skipping on this kind of mapping altogether.
Instead, just use the command-line window instead, which allows you to use any arbitrary normal mode commands to edit your command-line. You have the full power of Vim to edit your Vim command-lines, what's not to like?
You can enter it with CTRL-F
from the command-line itself. Or, if you're in normal mode, you can type q:
instead of :
to go straight to the command-line window to write your command from scratch.
You can then use gU{motion}
to make any parts of the command uppercase, in addition to the full power of normal mode commands.
Once you're happy with your edits, you can either press "ENTER" to execute the command directly, or if you wish to resume the command-line, you can press CTRL-C
to go back to the usual command-line, accepting the edits performed in the command-line window.