using a variable in a substitute command

26 views
Skip to first unread message

Chris Jones

unread,
May 20, 2021, 12:12:33 PM5/20/21
to vim...@googlegroups.com
I have a bunch of headers of the form:

| # <header content>

... in a few files conveniently named: f01.md, f02.md... fnn.md and
I was looking for a way to number these headers and add a constant
literal so that after processing they would look something like this:

| # \textit{<number>: <header content>}

While editing the files, I ran a quick test issuing the following
ex-mode commands:

| :let g:counter =0
| :bufdo :g/^#/let g:counter = g:counter +1 | echom g:counter

As expected this causes vim to display something like:

| 1
| 2
| 3
| ...
| 44
| 45

So far so good.

Now I tried to replace the "echom g:counter" command by a :s(ubstitute)
command, e.g.

| :s/^# \(.*$\)/# \\textit{\=g:counter: \0}

Unfortunately this is what I got:

| # \textit{=g:counter: <header content>}
| ... i.e.
| # \textit{=g:counter: title of chapter #1}
| # \textit{=g:counter: title of chapter #2}
| # \textit{=g:counter: title of chapter #3}
| etc.

In other words what gets substituted is (among other things) the *name*
of the variable NOT its contents.

Can this be fixed (syntax problem?) or should I take an altogether
different approach?

CJ

Tim Chase

unread,
May 20, 2021, 12:37:25 PM5/20/21
to Chris Jones, vim...@googlegroups.com
On 2021-05-19 19:16, Chris Jones wrote:
> Now I tried to replace the "echom g:counter" command by a
> :s(ubstitute) command, e.g.
>
> | :s/^# \(.*$\)/# \\textit{\=g:counter: \0}
>
> Unfortunately this is what I got:
>
> | # \textit{=g:counter: <header content>}
> | ... i.e.
> | # \textit{=g:counter: title of chapter #1}
> | # \textit{=g:counter: title of chapter #2}
> | # \textit{=g:counter: title of chapter #3}
> | etc.
>
> In other words what gets substituted is (among other things) the
> *name* of the variable NOT its contents.

So close.

The magic you're looking for is to *begin* your replacement with

\=

and everything that follows (up to the next delimiter, usually a "/")
is evaluated as an expression. So you can do something like

:s/^# \zs\(.*\)/\='\\textit{'.g:counter.': '.submatch(0).'}'

(I might have the number of escaping "\"s wrong before the "textit")

You can even use the filename itself (available in the "%" register)
in that expression, something like

s/^# \zs\(.*\)/\='\\textit{'.substitute(@%, 'f\(.*\).md', '\1',
'').': '.submatch(0).'}'

to capture the filename. That way, even if the buffer-list gets out
of order, you're still pulling in the expected file-number based on
the filename.

Hope this gives you the tools you need to feel like you can dominate
this task. :-)

-tim




Chris Jones

unread,
May 21, 2021, 12:24:37 PM5/21/21
to vim...@googlegroups.com
Aaaah... I get it. Basically I need to make *everything* part of the
'expression' (in vimdoc parlance) that will be substituted using the
concatenation dot ('.') to build said 'expression': so that I end up
with an object that joyfully mixes literals (such as '\textit}', ': ',
and the final '}') with variables (g:counter) and the return value of
function calls (i.e. submatch(0) — should actually be submatch(1) on
second thoughts)...!

I haven't tested your solution yet but it's obvious that it is going to
work... because it could not be otherwise.

Incidentally, I had thought about grabbing the numbers in the file names
but decided against it because apart from the added difficulty I was not
sure how I would be able to get rid of the leading zeroes where relevant
(f01—f09) but I get the point: the sky's the limit where vim trickery
is concerned.

And as to doubling the textit slash it's needed because vim gobbles up
the initial 't' of 'textit' otherwise .. not sure why.

Thanks,

CJ

Gary Johnson

unread,
May 21, 2021, 2:23:14 PM5/21/21
to vim...@googlegroups.com
On 2021-05-20, Chris Jones wrote:

> And as to doubling the textit slash it's needed because vim gobbles up
> the initial 't' of 'textit' otherwise .. not sure why.

In many contexts, Vim interprets '\t' as a tab character. See

:help /\t

Regards,
Gary

Chris Jones

unread,
May 21, 2021, 2:49:55 PM5/21/21
to vim...@googlegroups.com
Yes. Actually in my final effort:

| :bufdo :g /^# \(.*\)$/let g:c = g:c + 1 | s//\='# \textit{' . g:c . ': ' . submatch(1) . '}'

... probably because it's already 'escaped' due to the fact that's in
single quotes, I found that vim left the \textit backslash alone.

As expected Tim's solution does exactly what I needed.

Thanks,

CJ
Reply all
Reply to author
Forward
0 new messages