2

My question is about a very specific search and replace pattern, but I hope to find answers for the more general case. I am currently working on some LaTeX slides with allot of overlays and I need to increment certain integers by one or more. A sample text:

\only<1,5-7,9>{hello 11}

In short, the only command only shows hello 11 on slide number 1, 5 through 7 and 9. After inserting a slide at position 2, I need all slides above slide 2 to increment. This is what I came up with:

:.,$s/\d\+/\=str2float(submatch(0))>2?submatch(0)+1:submatch(0)/g

From the current line to the end of the file, it increments all integers above 2 by 1. This means that 11 is also incremented, which is not what I want:

\only<1,6-8,10>{hello 12}

Q1: How can I only match and increment the integers between the delimiters '<' and '>'?


Ultimately, I would like to be able to refine search patterns incrementally by specifying a pattern and apply another pattern to the result. For instance I first match the text between delimiters from the given example text.

/<[^<>]*>

which would highlight the text I have quoted:

\only"<1,5-7,9>"{hello 11}

And now apply my original solution to increment the numbers between the delimiters. I can compare what I would like to do with chaining grep commands together in a shell with the pipe symbol, refining the result with each new grep command.

Q2: Is this chaining of vim search patterns possible?


Update: The solution of Floris comes very close. Indeed it solves the original question, but I failed to mention all requirements. They are:

- The delimiters are guaranteed to be on one line, e.g., <1,5-7,9>.
- Multiple delimited parts can be on one line, e.g.:
   hello 11\only<1,6-7,9>{hello 11}\only<1,6-7,9>{hello 11}

The second requirement fails, as the solution currently returns:

hello 12\only<1,7-8,10>{hello 12}\only<1,7-8,10>{hello 11}

It only ignores integers after the last occurrence of '>'. I would like the result to be:

hello 11\only<1,7-8,10>{hello 11}\only<1,7-8,10>{hello 11}

Thanks for any help!

gospes
  • 3,819
  • 1
  • 28
  • 31
  • You are really trying to make this hard aren't you?!... I think I may be able to solve it if there are only commas, hyphens and digits in between the `<>`, but can't test right now and would like confirmation before I make another "nearly" answer... – Floris Feb 06 '14 at 12:54
  • I try :) I suppose instead of numbers between < and >, you could view it as numbers enclosed by characters [<>,-]. – gospes Feb 07 '14 at 10:36

4 Answers4

2

You need a lookahead construct - I think it is as follows:

:.,$s/[0-9]\+\(.*>\)\@=/\=str2float(submatch(0))>2?submatch(0)+1:submatch(0)/g

The thing I added is

\(.*>\)\@=

This is a "positive lookahead", a zero-width assertion that says

the thing before this must be followed by any number of characters followed by close bracket

You can find a bit more information about the syntax in vim help, or https://stackoverflow.com/a/18391881/1967396 (which is where I got it from).

Running the above, I did get

\only<1,6-8,10>{hello 11}

as the output - which is what I think you wanted.

Note: I tested this in vim on the Mac. It has been known to behave slightly differently than the linux version (well, I'm extrapolating from the sed differences, I don't actually know that vim is different too...) . If this isn't working, it should be jolly close.

Community
  • 1
  • 1
Floris
  • 45,857
  • 6
  • 70
  • 122
  • It truly comes very close. There is a case where it fails though, which is my bad, because I didn't mention it in my question. Although the delimiters are located on the same line, there can be multiple delimited parts on the line. I'll update my question. – gospes Feb 06 '14 at 08:55
1

You basically need to apply a search-and-replace (with an expression that can evaluate the conditional!) only at certain places (i.e. inside the <...> parts).

That's difficult, because you either need to:

  • carefully specify the match conditions with lookahead and lookbehind (as the other answers have attempted), or
  • match on the <...>, and perform another substitution inside, which Vim doesn't allow (recursive sub-replace-expression (\=...) isn't possible)

The new version 1.30 of my PatternsOnText plugin can handle this:

First, search for the <...> locations:

/<[^>]\+>

Then, formulate a special substitution that is applied only within the search results:

:%SubstituteInSearch/\d\+/\=submatch(0) > 2 ? submatch(0) + 1 : submatch(0)/g
Ingo Karkat
  • 167,457
  • 16
  • 250
  • 324
0

If you can assume that \only<.... will appear on a line of it's own, you can simply

:.,$g/\\only<\d/s/\d\+/\=str2float(submatch(0))>2?submatch(0)+1:submatch(0)/g

Which is, the :global command (:.,$g/\\only<\d/) used to filter the targeted lines, and then your "magic" subsitution unaltered :)

sehe
  • 374,641
  • 47
  • 450
  • 633
  • I think the problem is that the substitution continues with the number after `hello`. You need a `lookaround` expression ("digit in between `<` and `>`), and I'm not sure that vim's regex supports it? – Floris Feb 05 '14 at 21:50
  • @Floris ah... I expect the post to show the intended outcome... :{ – sehe Feb 05 '14 at 21:57
  • that would have been reasonable; but he _did_ say "This means that 11 is also incremented, which is not what I want". – Floris Feb 05 '14 at 23:16
0

The question about regex is of course relevant, but concerning your too numerous overlays, maybe you should consider using names instead of numbers: a solution is available on Tex-Latex at:

https://tex.stackexchange.com/questions/34458/reference-overlay-numbers-with-names?rq=1

Community
  • 1
  • 1
Joce
  • 2,220
  • 15
  • 26