6

I have a need to replace this:

fixed variable 123

with this:

fixed variable 234

In VSCode this matches fine:

fixed(.*)123

I can't find any way to make it put the capture in the output if a number follows:

fixed$1234 

fixed${1}234

But the find replace window just looks like this:

enter image description here

I read that VSCode uses rust flavoured rexes.. Here indicates ${1}234 should work, but VSCode just puts it in the output..

Tried named capture in a style according to here

fixed(?P<n>.*)123     //"invalid regular expression" error

VSCode doesn't seem to understand ${1}: enter image description here


ps; I appreciate I could hack it in the contrived example with

FIND: fixed (.*) 123
REPL: fixed $1 234

And this does work in vscode:

enter image description here

but not all my data consistently has the same character before the number

Caius Jard
  • 72,509
  • 5
  • 49
  • 80
  • 1
    I've hacked it as a two step replace, first replacing with `xxx$1'234` then replacing `'234` with `234` but I'm still keen to know what the single step approach is – Caius Jard Jul 11 '20 at 14:32
  • 1
    Okay, you aren't doing a find/replace , you are doing a search across files/replace. The problem doesn't happen in the find/replace widget. Since you do have a space before the digits include that in your capture group like `(.*?)( \d{1,})` and replace with `$1 234`. There is a space before the `\d`, see https://regex101.com/r/kBGDnc/3 – Mark Jul 11 '20 at 14:34
  • I would say it is a bug in the search/replace functionality. – Mark Jul 11 '20 at 14:34
  • I don't know why but this seems to work `(.*?)((.)\d{3})` replace with `$1$3234` so that `fixed variable!123` does become `fixed variable!234`. Definitely a bug. See https://regex101.com/r/kBGDnc/5. – Mark Jul 11 '20 at 14:41
  • 1
    @Mark Actually, I see that even `$2` alone is causing an issue. Your workaround works when there are two or more consecutive backreferences in the replacement pattern. OP can use `fixed()(.*)123` and replace with `fixed$1$2234` where the first capturing group matches an empty space (it is "technical" here) and the second group captures the text between `fixed` and `123`. – Wiktor Stribiżew Jul 11 '20 at 14:49
  • @Wiktor nice +1 So put in a `()` matching nothing just to get an extra capture group. Then use `(.*?)()(\d{3})` or similar but with a "match nothing" `()` capture group placed such that you will end up with two consecutive capture groups (one empty) in the replace field. `$1$2234` to replace. – Mark Jul 11 '20 at 14:57
  • 1
    @Caius Sorry I missed `but not all my data consistently has the same character before the number`. In the future I would put more/all input examples where they vary together where you say what you need to match. Then someone can just copy all your input versions in one go and test easily. – Mark Jul 11 '20 at 15:06
  • @Wiktor Okay if I put this into an answer? – Mark Jul 11 '20 at 15:07
  • As you wish, Mark. `fixed(.*)123` -> ``fixed$`$1234`` also seems to work. ``"$`"`` seems to be ignored. – Wiktor Stribiżew Jul 11 '20 at 15:07
  • @Wiktor Just curious as to why you tried $backtick? – Mark Jul 11 '20 at 15:50
  • @Mark Just to see if it has any impact on the replacement. Both `$'` (usually means *all the text of the input string after the match in the replacement string*) and ``"$`"`` (without quotes, means *all the text of the input string before the match in the replacement string*) act as empty placeholders here, which is strange. It is probably another bug. The named capturing group syntax supported here is `(?...)`, as in .NET or PCRE, but the replacement does not accept Boost-like `$+{name}`. Named captures cannot be used in the replacement pattern here, like in PHP. – Wiktor Stribiżew Jul 11 '20 at 15:53
  • @Mark I added another answer where I explained my reasoning. I think we need to either file two more issues, or expand the current one. – Wiktor Stribiżew Jul 11 '20 at 18:27

2 Answers2

8

After a lot of investigation by myself and @Wiktor we discovered a workaround for this apparent bug in vscode's search (aka find across files) and replace functionality in the specific case where the replace would have a single capture group followed by digits, like

$1234 where the intent is to replace with capture group 1 $1 followed by 234 or any digits. But $1234 is the actual undesired replaced output.

[This works fine in the find/replace widget for the current file but not in the find/search across files.]

There are (at least) two workarounds. Using two consecutive groups, like $1$2234 works properly as does $1$`234 (or precede with the $backtick).

So you could create a sham capture group as in (.*?)()(\d{3}) where capture group 2 has nothing in it just to get 2 consecutive capture groups in the replace or

use your intial search regex (.*?)(\d{3}) and then use $` just before or after your "real" capture group $1.

OP has filed an issue https://github.com/microsoft/vscode/issues/102221


Oddly, I just discovered that replacing with a single digit like $11 works fine but as soon as you add two or more it fails, so $112 fails.

Mark
  • 143,421
  • 24
  • 428
  • 436
3

I'd like to share some more insights and my reasoning when I searched for a workaround.

Main workaround idea is using two consecutive backreferences in the replacement.

I tried all backreference syntax described at Replacement Strings Reference: Matched Text and Backreferences. It appeared that none of \g<1>, \g{1}, ${1}, $<1>, $+{1}, etc. work. However, there are some other backreferences, like $' (inserts the portion of the string that follows the matched substring) or $` (inserts the portion of the string that precedes the matched substring). However, these two backreferences do not work in VS Code file search and replace feature, they do not insert any text when used in the replacement pattern.

So, we may use $` or $' as empty placeholders in the replacement pattern.

Find What:      fix(.*?)123
Replace With:

  • fix$'$1234
  • fix$`$1234

Or, as in my preliminary test, already provided in Mark's answer, a "technical" capturing group matching an empty string, (), can be introduced into the pattern so that a backreference to that group can be used as a "guard" before the subsequent "meaningful" backreference:

Find What: fixed()(.*)123 (see () in the pattern that can be referred to using $1)
Replace With: fixed$1$2234

Here, $1 is a "guard" placeholder allowing correct parsing of $2 backreference.

Side note about named capturing groups

Named capturing groups are supported, but you should use .NET/PCRE/Java named capturing group syntax, (?<name>...). Unfortunately, the none of the known named backreferences work replacement pattern. I tried $+{name} Boost/Perl syntax, $<name>, ${name}, none work.

Conclusion

So, there are several issues here that need to be addressed:

  • We need an unambiguous numbered backerence syntax (\g<1>, ${1}, or $<1>)
  • We need to make sure $' or $` work as expected or are parsed as literal text (same as $_ (used to include the entire input string in the replacement string) or $+ (used to insert the text matched by the highest-numbered capturing group that actually participated in the match) backreferences that are not recognized by Visual Studio Code file search and replace feature), current behavior when they do not insert any text is rather undefined
  • We need to introduce named backreference syntax (like \g<name> or ${name}).
Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563