3

I want to set up a shortcut to make word under the cursor upper case in the command mode. I know that you can get the word under the cursor with expand("<cword>"). But I am not sure how do I translate it to upper case the then replace the original word in command mode, because U, ~ will not work in command mode.

So I can only come up with the following mapping, which is missing the right-hand side expression:

cnoremap <c-u> xxx

4 Answers 4

5

Assuome current command :

echo   "hello   world"
       ^

^ marks current cursor, after upcase current word, it becomes:

echo   "HELLO   world"
             ^

The key part is to split your command into 3 parts:

echo   "hello   world"
--1----^     ---3-----
       ---2--

This can be done with :h /\%c :

echo matchlist('echo   "hello   world"', '\v^(.*)(%8c\s*\S+)(.*)')

You can use :h getcmdline() to get current command, :h getcmdpos() to get cursor position, :h setcmdpos() to set cursor position, :h c_CTRL-\_e to replace the entire command :

function! s:cmd_word_case(up)

  " split cmdline into 3 parts
  let matches = matchlist(getcmdline(),
        \ printf('\v^(.*)(%%%dc\s*\S+)(.*)', getcmdpos()))
  if len(matches) < 4
    return getcmdline()
  endif

  let [pre, word, post] = matches[1:3]
  call setcmdpos(len(pre) + len(word) + 1)

  return pre . (a:up ? toupper(word) : tolower(word)) . post

endfunction

cnoremap <a-u> <c-\>e<sid>cmd_word_case(1)<cr>
cnoremap <a-l> <c-\>e<sid>cmd_word_case(0)<cr>
6
  • Thanks for your answer, but it seems that it does not work. For example, I have :echo myvar in command line and the cursor is in myvar. Press the shortcut does not turn it into upper case.
    – jdhao
    Commented Jul 21, 2019 at 7:36
  • @jdhao Did the cursor position changed? What is the exact position of your cursor before you upcase it ?
    – dedowsdi
    Commented Jul 21, 2019 at 7:41
  • The cursor position is after r in myvar. I have found that only characters after the cursor position will be turned into upper case. For example, if the cursor position is after y in myvar, only var will be turned into upper case.
    – jdhao
    Commented Jul 21, 2019 at 7:44
  • @jdhao That's the desired behavior, it's the same as emacs mode (which i don't use - -) in zsh.
    – dedowsdi
    Commented Jul 21, 2019 at 7:46
  • 2
    @jdhao Add <c-left> before <c-\> .
    – dedowsdi
    Commented Jul 21, 2019 at 8:05
8

Rather than make a mapping (particularly over top of the helpful <C-u> kill-line), I suggest the following:

  1. Open the command-line window. q: from normal mode, or <C-f> if already typing a : command.
  2. Use vim’s editing commands, such as gUiw
3
  • 1
    Personally, I never use <c-u>. But I can settle for other shortcuts. The question is how can I turn current words into upper case in the command line. I am aware of the command window.
    – jdhao
    Commented Jul 20, 2019 at 13:48
  • Nice!, btw, guiw will conveniently convert the entire word to lower case. I appreciate not having to use the command line to accomplish either. Up to now I've used ~ to flip case and arrow or equivalent to skip characters already in the desired case. gUiw is less tedious and less error prone. thx! Commented May 17 at 6:15
  • 1
    @ShpielMeister you are probably interested in reading :help case and :help motion: there is a vast number of ways to combine operators in motions, such that gu/foo<enter> and other interesting ideas are not only possible but encouraged.
    – D. Ben Knoble
    Commented May 17 at 13:00
6

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.

3
  • 2
    but it doesn't seem like it's possible to dismiss that window upon returning to the command-line I believe you have Neovim in mind, as in Vim it's enough to have set lazyredraw.
    – Matt
    Commented Jul 22, 2019 at 7:39
  • @Matt 'lazyredraw' defaults to off and I don't think you can really disable it from a cnoremap using CTRL-F. But yeah if you're willing to change the global setting, and not using NeoVim, then that's a solution. Thanks!
    – filbranden
    Commented Jul 22, 2019 at 13:30
  • 1
    Thanks for this great answer!
    – jdhao
    Commented Jul 22, 2019 at 15:41
5

The question is how can I turn current words into upper case in the command line. I am aware of the command window.

Simply open the command window, process the command, and get back to the command line, all inside a single mapping:

cnoremap <C-U> <C-F>gUiww<C-C>

" prevents the command-line window from remaining open after <C-C>
" works in Vim (but not in Neovim)
set lazyredraw

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.