1

I'm trying to write a Visual Studio Code snippet that converts Pascal case to lowercase with spaces. I'm almost there:

${1/([A-Z]*)([A-Z][a-z]+)/$1 ${2:/downcase} /g}

Group 1 matches acronyms and group 2 matches capitalized words.

So if the placeholder were MyUIDTest, the expected result would be my UID test. But currently I get my UID test (notice the spaces on either side).

How do I only add a space after group 1 if group 1 has a match? And how do I remove the space at the end of the line?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
JakesMD
  • 1,646
  • 2
  • 15
  • 35
  • [Here a PCRE idea at regex101](https://regex101.com/r/WBa4df/3) (you'd need to adjust it). – bobble bubble Aug 05 '23 at 14:16
  • 1
    @bobblebubble `(\B)?` would not work in ECMAScript at all. See [this unanswered question](https://stackoverflow.com/q/76579881) of mine. – InSync Aug 05 '23 at 20:14
  • @InSync Interesting, never came across this yet, very nice! So maybe something like [this update](https://regex101.com/r/WBa4df/4) could also work (replacement needs to be adjusted to the other syntax). – bobble bubble Aug 06 '23 at 04:52
  • 1
    @bobblebubble I'm not sure if VSCode's snippet syntax allow nested conditions. According to the grammar described [here](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_grammar), `if` and `else` equals to `text`, which has no `format`, but only `.*`. – InSync Aug 06 '23 at 05:37
  • @InSync There is no need to nest the conditions, it can be written [like this](https://regex101.com/r/WBa4df/5) as well. – bobble bubble Aug 06 '23 at 14:04
  • 1
    @bobblebubble Nesting `format` also doesn't work, but `$1${1:+ }$3${3:+ }${2:/downcase}` does. You should post that as an answer. – InSync Aug 06 '23 at 14:28
  • @InSync I'm not answering currently. Thanks for testing it so we could find some alternative idea! – bobble bubble Aug 06 '23 at 14:37
  • 1
    I posted it anyway (as CW). Hope you don't mind. – InSync Aug 06 '23 at 14:52

3 Answers3

3

According to the docs, ${1:+ } means "insert a space iff group 1 is captured":

${
  TM_SELECTED_TEXT
  /
    (?<=([A-Za-z])?)      # Lookbehind for and capture ($1) a preceding letter, if any,
    (?:                   # then match a "word" – either
      ([A-Z]+)(?![a-z])   # ($2) 1+ uppercase not followed by a lowercase
    |                     # or
      ([A-Z][a-z]+)       # ($3) an uppercase followed by 1+ lowercase.
    )                     #
  /                       # Replace each match with
    ${1:+\u0020}          # a space, iff group 1 is captured,
    ${2:/upcase}          # the uppercase version of group 2
    ${3:/downcase}        # and the lowercase version of group 3.
  /g                      # for all matches found.
}

Since $2 and $3 are mutually exclusive, using them both always results in only one being actually inserted.

Before After
PascalCase pascal case
MyUIDTest my UID test
FoOBaR fo O ba R

A more precise (and verbose) version would be (note that \b needs to be escaped again when written in a JSON string: "\\b"):

${
  TM_SELECTED_TEXT
  /
    (?<=                                    # Lookbehind for
      \b                                    # a word boundary followed by
      ([A-Z]+(?![a-z])|[A-Z][a-z]+)*        # 0+ "words"
                                            # and capture the last word, if any.
    )
    (?:([A-Z]+)(?![a-z])|([A-Z][a-z]+))
  /
    ${1:+\u0020}${2:/upcase}${3:/downcase}
  /g
}

I'm using TM_SELECTED_TEXT here, but you might want to change that depending on your needs.

InSync
  • 4,851
  • 4
  • 8
  • 30
1

I think to remove the space at the end of the line,you can simply add a newline character at the end of the replacement pattern, and when you use the snippet with the placeholder "MyUIDTest", it should correctly produce "my UID test" without any extra spaces.

${1/(?:([A-Z]+)(?=[A-Z][a-z]))?([A-Z][a-z]+)/$1 ?${2:/downcase} :''/g}\n

also to add a space after group 1 only if it has a match, you can use a conditional statement in the replacement pattern

${1/(?:([A-Z]+)(?=[A-Z][a-z]))?([A-Z][a-z]+)/$1 ?${2:/downcase} :''/g}
Freeman
  • 9,464
  • 7
  • 35
  • 58
1

Credit goes to @bobblebubble:

${
  TM_SELECTED_TEXT
  /                     # Match either
    (\w)?               # an optional group containing a word character 
    ([A-Z])             # then a group containing an uppercase
    (?=[a-z])           # which is followed by a lowercase
    |                   # or
    ([a-z])(?=[A-Z])    # vice versa.
  /                     #
    $1${1:+ }           # If group 1 was captured, put it back followed by a space.
    $3${3:+ }           # Do the same with group 3.
    ${2:/downcase}      # Finally, insert a lowercased version of group 2.
  /g
}
InSync
  • 4,851
  • 4
  • 8
  • 30