Taking a word or otherwise long argument (longer than a single char) from a mapping usually involves prompting the user, using the input()
function.
But that's often not very convenient, as you're somewhat slowed down by the prompting and it's also hard to invoke or reuse that mapping from a non-interactive context.
You'll probably find that using a user-defined command is typically a more handy way to expose features that take longer arguments.
In any case, let's create both.
Let's actually start by writing a function to perform the substitution, including the arbitrary word. Even if we didn't make the function public, it would make implementing either the mapping or the user-command easier.
function! SubstBrackets(word)
let word_re = '\V'.escape(a:word, '/\')
let pattern = '\v<('.word_re.'\v)\((.{-})\)'
execute '%s/'.pattern.'/\1{\2}/eg'
endfunction
This function is a somewhat more sophisticated way to perform the substitution you described. It uses "very magic" and "very nomagic" mode together with escape()
to ensure special characters in the word will not interfere.
It will also match the word at a word boundary (the <
at the start of the match, you might have seen it as \<
before, but we're in "very magic" mode here.) It matches the terms inside parens using non-greedy .{-}
match to find the nearest closing paren. Finally, it uses two capturing groups, so the word doesn't need to be repeated on the replacement side.
You can test it with:
:call SubstBrackets('interior')
(We could go a step further in sophistication here by making SubstBrackets
a "range" function, rather than implicitly acting on %
for the whole buffer. See :help :func-range
for more details on implementing a range function.)
At this point, we can implement our ;subst
mapping using this function, using:
nnoremap <silent> ;subst :call SubstBrackets(
\ input('Word to replace? ', 'interior'))<CR>
One downside of this mapping is that typing ;subst
possibly without visual feedback can be awkward. If you type a single character incorrectly, you can't fix it with "backspace" either, you need to break execution of the macro altogether (with Ctrl+C or similar), then start again.
I briefly discussed how input()
can be awkward in interrupting the flow with a prompt and also making it hard to reuse this mapping in a less interactive context. On the upside, input()
lets you easily define a default text to use if user simply presses "enter", which can be nice too... (input()
also allows you to specify how to do completion of the term you're typing, which can be useful too!)
Now I mentioned creating a user-defined command was a possibly better interface for this feature. This is how you would define one:
command! -bar -nargs=1 SubstBrackets
\ call SubstBrackets(<q-args>)
And then you can use it with:
:SubstBrackets interior
Note that you can use tab completion here, so you can simply type :Su<Tab>
and Vim will complete the command for you!
This interface tends to be better, since you can use this command from many other contexts, such as functions or if
or for
statements, etc.
Same as with input()
, you can specify a completion type to use with the argument of this function. So even though you don't get the descriptive prompt, you do get one of the other benefits of input()
here.
You could also implement a default value, by using -nargs=*
and changing the function to take variable number of arguments, then checking the number of passed parameters and using a default if none was passed. (In fact, that also allows you to receive multiple arguments, which you could also handle by substituting bracket types in all passed words!)
Let's also explore allowing a range to be passed here. By turning the function into a range
function and passing a -range=%
to the command definition, we can take an optional range, while still acting on the whole buffer if no range is passed.
Here's a modified complete version of the command, that takes an optional range:
function! SubstBrackets(word) range
let word_re = '\V'.escape(a:word, '/\')
let pattern = '\v<('.word_re.'\v)\((.{-})\)'
let range = a:firstline.','.a:lastline
execute range.'s/'.pattern.'/\1{\2}/eg'
endfunction
command! -bar -nargs=1 -range=% SubstBrackets
\ <line1>,<line2>call SubstBrackets(<q-args>)
Hopefully I've successfully demonstrated how and why the user-defined command version of this feature is the most flexible and usable one!
:h user-commands
, not a mapping.q
command and replaying it with@
. What you're describing here is typically referred to as a mapping. Would you consider to edit the question and use the term "mapping" rather than "macro"? I think that would improve the question. Thanks!