Maybe the title isn't to explicative; feel free to improve it.

I have the text below:

1. Intro          |timetracking-intro|$
2. Uso            |timetracking-usage|$
3. Comandi        |timetracking-commands|$

I want to replace all space between the title and the tag with dots, but the first and the last, like this:

1. Intro ........ |timetracking-intro|$
2. Uso .......... |timetracking-usage|$
3. Comandi ...... |timetracking-commands|$

After an hour of attempts without success I can't understand this:

  1. :s/\A\s\A/./gc
    This match also the firts space after the word "intro". Why? I used \A to not match the first space! (in the picture I grouped, \(\A\s\A\) but is the same).
    enter image description here

  2. :s/\(\A\zs\s\ze\A\)/./gc
    but this regex match only alternate spaces, and match also the last space!!
    enter image description here

I had also some more funny results... :-/

Can anyone explain me why and how to do what I want?

  • 1
    \A means non-alphabetic character (:help \A).
    – D. Ben Knoble
    Commented Jul 21, 2023 at 18:55
  • Do you still have something open in your question? How can we help you further? Otherwise may be could accept one of the answers using the v button next to the arrow voting buttons. It allow the question to rest :-) Commented Jul 22, 2023 at 16:11

3 Answers 3


I would propose you:



  • \s\@<= match only if the previous character is a space
  • \s\@= match only if the next character is a space

Since both match with zero width you don't have the one out of two effect.

  • All the answers works, but this is the more elegant and compact. I didn't understand the zero-width matches with \@=, \@!, etc . Could you show me some link to read for a better explanation than the pattern.txt?
    – Antonio
    Commented Jul 23, 2023 at 22:40
  • Excuse me @Vivian, I'm thinking about this solution and my second try. Except for the mistake at the end (\A match the |) tell me if I understand: I see highlighted alternated spaces because the match include three characters, and then the next match start after the previous? For this reason you use \@=? In effect my "2." corrected with the "\@"works: :s/\(\A\@<=\zs\s\ze\s\@=\)/./gc. The \zs and \ze in this case is redundant, and simplifying this I reach the compactness of your answer. The clue is \@= and \@<=, right?
    – Antonio
    Commented Jul 23, 2023 at 23:02
  • 1
    @Antonio, you are right :-) I have made other tests and the culprit seems to be \zs: \s\zs match with zero with but consume one character (a space). It seems that \s\@<=\s\ze\s is also a solution. Commented Jul 24, 2023 at 7:25

Try this:

:%s/  \+/\=' '.repeat('.',len(submatch(0))-2).' '/g

%s/PATTERN/\=EXPR/g substitutes all occurrences of PATTERN with the result from evaluating EXPR

\+ matches at least 2 spaces

len(submatch(0)) is the length of the currently matched text

repeat('.',len(submatch(0))-2) creates the sequence of dots

' '. .' ' surrounds it with spaces

  • This answer would be improved by explanation
    – D. Ben Knoble
    Commented Jul 21, 2023 at 18:57

There may be a better way, but this is what I came up with:

:s/\S\s\zs\s*\ze\s\S/\=substitute(submatch(0), '\s', '.', 'g')/

\A matches anything that isn't an alphabetic character, so it makes sense that it matches a space. Instead, you'd want to use \S to find the boundaries of the spaces.

The pattern part can be explained as: "Find a non-space followed by a space. Then find 0 or more spaces, which will be the part we operate on in the replacement part. Then find another space followed by a non-space".

The replacement part then uses \=substitute() to then do a find/replace on the spaces found between the \zs and \ze. I'm not sure if there's another way to dynamically do a replacement when there's a variable number of characters in the captured match.

  • Since you already limit the match with zs/ze, and it can only be spaces, you probably want repeat('.', len(submatch(0))) or possibly a use of tr()
    – D. Ben Knoble
    Commented Jul 22, 2023 at 12:50

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.