21

I'd like to create a VS Code snippet for creating redux reducers.

I would like to have a snippet with placeholder that expects camelCase and then transform a matching placeholder to SCREAMING_SNAKE_CASE.

Here's my attempted snippet, which is not working:

"test": {
    "prefix": "test",
    "body": "${1} -> ${1/([a-zA-Z])(?=[A-Z])/${1:/upcase}_/g}"
},

Which produces a non-desired result:

changeNetworkStatus -> changE_NetworK_Status

Desired Flow

  1. type test (name of snippet)
  2. hit tab to load the snippet.
  3. type changeNetworkStatus to result in:

    changeNetworkStatus -> changeNetworkStatus
    
  4. hit tab to get expected result of:

    changeNetworkStatus -> CHANGE_NETWORK_STATUS
    

How can I change my snippet code to get the desired result?

Here's a related solution which requires a different flow.

Mark
  • 143,421
  • 24
  • 428
  • 436
Beau Smith
  • 33,433
  • 13
  • 94
  • 101
  • Which version of VS Code? Does provided answer work for you? – Braca Jul 23 '18 at 14:38
  • @Braca - Version 1.25.1 - Yes provided answer does work. Why? Do you have better solution? – Beau Smith Jul 23 '18 at 21:50
  • No, the answer is great. I've had a problem, it worked 50% of the time (same version). Solved by pressing 'esc'. Just wondering if that's the standard behavior or I have some conflicting settings. – Braca Jul 24 '18 at 08:06
  • Not sure what you mean by having to press 'esc' - using a prefix that is unique, like "_sc" might fix that for you. The problem with "test" in the answer is that that could easily show up in your own intellisense suggestions from something else and hitting "tab" would autocomplete that other thing if you weren't careful to select this actual snippet for expansion. That is why I would always use a prefix that isn't so common as "test". – Mark Jul 24 '18 at 21:40
  • @Mark The default setting is: `"javascript.nameSuggestions": true` , when set to 'false' the problem is gone – Braca Jul 25 '18 at 08:51
  • Yes, because intellisense then does not pick up random words from the file that fuzzy match the prefix. Or use a unique prefix. – Mark Jul 25 '18 at 13:52

1 Answers1

30

If you are starting with a non-camelCase input and want to get to SCREAMING_SNAKE_CASE, see https://stackoverflow.com/a/67008397/836330. The method there can handle input with spaces and hyphens.


Update: Keybinding version:

VScode is adding the editor.action.transformToSnakecase in v1.53 so the requested operation can be done easier without having to figure out the neccessary regex to make it work as shown in the previous answer. And because some people might find this question looking for snake case (snake-case) information.

What I show now is NOT a snippet however. You just type your text and then trigger the keybinding. The keybinding itself fires a macro extension command from the multi-command extension. In keybindings.json:

      {
        "key": "alt+3",                        // whatever keybinding you wish
        "command": "extension.multiCommand.execute",
        "args": {
          "sequence": [
            "cursorWordLeftSelect",            // select word you just typed
            "editor.action.transformToSnakecase",
            "editor.action.transformToUppercase",
            // "cursorLineEnd"                   // if you want this
          ]
        },
        "when": "editorTextFocus && !editorHasSelection"
      },

Demo of keybinding version:

snake case demo


Snippet version:

"camelCaseModify": {
    "prefix": "test",       
    "body": [
       
       //  first inefficient try, works for up to three words
       //  "${1} -> ${1/^([a-z]*)([A-Z])([a-z]+)*([A-Z])*([a-z]+)*/${1:/upcase}_$2${3:/upcase}${4:+_}$4${5:/upcase}/g}"

       "${1} -> ${1/([a-z]*)(([A-Z])+([a-z]+))?/${1:/upcase}${2:+_}$3${4:/upcase}/g}",

// here is an especially gnarly version to handle edge cases like 'thisISABCTest' and trailing _'s
       "${1} -> ${1/([a-z]+)(?=[A-Z])|([A-Z])(?=[A-Z])|([A-Z][a-z]+)(?=$)|([A-Z][a-z]+)|([a-z]+)(?=$)/${1:/upcase}${1:+_}$2${2:+_}${3:/upcase}${4:/upcase}${4:+_}${5:/upcase}/g}"
        
    ],
    "description": "underscore separators"
},

This works with any number of camelCase words, from one to infinity...

The ${2:+_} means "if there is a capture group 2 then append an underscore." If there isn't a second word/capture group then groups 3 and 4 will be empty anyway because they are within capture group 2. Capture Group 2 is always the next Word (that starts with one capital and followed by at least one small letter).

for example, using changeNetworkStatus:

Match 1

Full match    0-13    `changeNetwork`
Group 1.      0-6     `change`
Group 2.      6-13    `Network`
Group 3.      6-7     `N`
Group 4.      7-13    `etwork`

Match 2

Full match    13-19    `Status`
Group 1.      13-13     ``
Group 2.      13-19     `Status`
Group 3.      13-14     `S`
Group 4.      14-19     `tatus`

Match 3

Full match    19-19    ``
Group 1.      19-19    ``

Sample Output:

abcd -> ABCD
twoFish -> TWO_FISH
threeFishMore -> THREE_FISH_MORE
fourFishOneMore -> FOUR_FISH_ONE_MORE
fiveFishTwoMoreFish -> FIVE_FISH_TWO_MORE_FISH
sixFishEelsSnakesDogsCatsMiceRatsClocksRocks -> SIX_FISH_EELS_SNAKES_DOGS_CATS_MICE_RATS_CLOCKS_ROCKS

Using regex101.com really helps to visualize what is going on!

Mark
  • 143,421
  • 24
  • 428
  • 436
  • If anyone is struggling to get this working, it doesn't run on type but runs on tab. – protoEvangelion Dec 17 '19 at 02:26
  • Nice! Unfortunately, this trick doesn't work for "AbcDef" -> "abc_def" since the first item cannot serve as a guard. Couldn't come up with a proper trick for this one (always end up with "abc_def_" since I cannot differentiate the end of the string). – Qortex May 13 '21 at 12:02
  • @Qortex which version (keybinding/macro or snippet) are you interested in? I have a solution for both for the form `AbcDef` `AbcDefGhiJkl...`. Post a new question and link here so I see it. – Mark May 13 '21 at 21:50
  • Thanks! Indeed, here is the post: https://stackoverflow.com/questions/67566628/how-to-convert-snippet-placeholder-from-camelcase-to-snake-case – Qortex May 17 '21 at 08:51
  • there's an edge case where this doesn't work: `thisIsATest` becomes `THIS_IS_TEST` instead of `THIS_IS_A_TEST`. Capital letters preceeded by a lowercase letter should get an underscore inserted before them, as well as capital letters followed by a lowercase letter. – Andy Jul 16 '21 at 19:33
  • @Andy I edited the answer with a snippet version which I believe handles those edge cases - it is significantly more complicated. In any case, the keybinding macro version does hande your edge case as it was. – Mark Jul 16 '21 at 21:52