Skip to content

Instantly share code, notes, and snippets.

@b0bu
Last active May 20, 2021 12:37
Show Gist options
  • Save b0bu/dfdc12a35e181713d56e5dac9ad35b63 to your computer and use it in GitHub Desktop.
Save b0bu/dfdc12a35e181713d56e5dac9ad35b63 to your computer and use it in GitHub Desktop.
vscode snippet keybinding string to list, list to string

I want to create a list of items from a string that would be useful in yaml json or python, so ["1","2"] from 1 2 (and back again). First understand you have to match a pattern before you can manipulate it. Second understand exactly what that pattern is. My pattern is a list of yum packages I'd like to quickly move to a yaml list for ansible. Here I have alphanumeric words with 1 sometimes 2 dashes. I have a word with no dashes and one with a dot ..

python2-mock python-zope-interface pytz pyOpenSSL.x86_64

The answer is, or at least my answer is

[
    {
        "key": "ctrl+l",
        "command": "editor.action.insertSnippet",
        "args": {
          "snippet": "$1[${TM_SELECTED_TEXT/(\\w+-?\\.?\\w+-?\\w+)(\\s)?/\"$1\"${2:+, }/g}]"
        }
      }
]
# ctrl+l
["python2-mock", "python-zope-interface", "pytz", "pyOpenSSL.x86_64"]

This is a specific match, not a fuzzy one. I'll explain how I got here. You can add snippets to the snippets section and tie them to a keybinding. Or you can create a keybinding and inline the snippet, which is what I'll do here.

cmd+shift+p for command palette and enter "short" open "Open Keyboard Shortcuts (JSON)" not "Open Default Keyboard Shortcuts (JSON)"

basic syntax

[
    {
        "key": "ctrl+l",
        "command": "editor.action.insertSnippet",
        "args": {
          "snippet": "${TM_SELECTED_TEXT}"
        }
      }
]

${TM_SELECTED_TEXT} is the highlighted text the snippet will act on. Selecting the list and pressing ctrl+l I just get the same list echoed back at me.

python2-mock python-zope-interface pytz pyOpenSSL.x86_64

In order to manipulate this text I have a standard sed like syntax s/something/tosomethingelse/g without the inital s, this goes inside the braces {} of the variable.

[
    {
        "key": "ctrl+l",
        "command": "editor.action.insertSnippet",
        "args": {
          "snippet": "${TM_SELECTED_TEXT/python/I-changed-the-thing/g}"
        }
      }
]
# ctrl+l
I-changed-the-thing2-mock I-changed-the-thing-zope-interface pytz pyOpenSSL.x86_64

Now lets talk about $1 $2. These are confusingly used for two things in the snippet: line. Outside of the ${VAR} they're called "tabstops". They're where you want your cursor to focus after you use the key binding and again after each tab key is pressed. Inside ${VAR/(regex)/${1}/g} they're positionally used to reference regex capture groups, i.e. /(group1)(group2)(group3)/$1 $2 $3/g.

[
    {
        "key": "ctrl+l",
        "command": "editor.action.insertSnippet",
        "args": {
          "snippet": "$1${TM_SELECTED_TEXT/python/I-changed-the-thing/g}"
        }
      }
]

With $1 at the start of the line,using keybinding ctrl+l my cursor will be at the start of the line instead of the end or wherever I left it.

So how do we make a list? Well since plaintext is honored wrappering the text in whatever you'd like is easy. Just wrap it in []

[
    {
        "key": "ctrl+l",
        "command": "editor.action.insertSnippet",
        "args": {
          "snippet": "$1[${TM_SELECTED_TEXT/python/I-changed-the-thing/g}]"
        }
      }
]

This would be fine for a bash array using (), but I need quotes and commas.

# ctrl+l
[I-changed-the-thing2-mock I-changed-the-thing-zope-interface pytz pyOpenSSL.x86_64]

If we get rid of the "I-changed-the-thing" shtick, we can get back to "understand what the pattern is" we're trying to match. Regex gets complicated REALLY fast so like anything else we just need to break up the problem and reduce it to its simplest terms later.

Note the regex here needs to be escaped so where \s and \w would be normal it'll be \\s and \\w.

If you start by replacing all spaces with comma-space , , it's a start. This will eventually be capture group2 futher down.

[
    {
        "key": "ctrl+l",
        "command": "editor.action.insertSnippet",
        "args": {
          "snippet": "$1[${TM_SELECTED_TEXT/\\s/, /g}]"
        }
      }
]
# ctrl+l
[python2-mock, python-zope-interface, pytz, pyOpenSSL.x86_64]

For determining the specific pattern I just googled regex playground and went to the first one that came up, using this interface and the list in this gist I could start to find what matched best for the pattern I needed. I'm also going to wrap the regex in a caputre group () so that I can reference the items with $number variable.

  • \w alphanumeric word based on its characters w o r d, meaning any changes, like wrapping in "" or adding a , will be applied to the chars (not what we want, think "mystring".split() in python.)

  • \w+ alphanumeric word as a block word so "my string".split(" ") in python.

  • \w+-\w+ will match word-word but not word-word-word

  • \w+-\w+-\w+ will match word-word-word but not word-word

and neither of them will match word or word.word which we need. The ? is used to tell the matching engine that part of the pattern is optional.

  • \w+-?\w+-?\w+ will match word word-word and word-word-word wince ?\w+- is now optional

All that's left is to match the word.word, since the . comes after the first word we'll place a \.

  • \w+-?\.?\w+-?\w+ this is an explicit match for the given list, not a fuzzy one and will match all of the above.
[a b c] <--- doesn't account for letters
["abc",  "abc",  "abc", ]
["abc.def",  "ghi.jlm", ]
["this-is",  "a-test-with",  "dashes", ]

Note again when these are added to the capture group () all \ are escaped with \\ and the quotes around the$1 var are also escaped. This wraps the items in quotes then adds ,

[
    {
        "key": "ctrl+l",
        "command": "editor.action.insertSnippet",
        "args": {
          "snippet": "$1[${TM_SELECTED_TEXT/(\\w+-?\\.?\\w+-?\\w+)/\"$1\", /g}]"
        }
      }
]
# ctrl+l
["python2-mock",  "python-zope-interface",  "pytz",  "pyOpenSSL.x86_64", ]

But we still have , to deal with at the end. We're wrapping capture group1 in quotes and adding , so this output makes sense, each item has , appended. We want to be able to control where the comma is added.

Here I've wrapped what capture group one matches in square brackets.

[python2-mock] [python-zope-interface] [pytz] [pyOpenSSL.x86_64]

If I create a second capture group I can identify the remaining spaces inbetween, illustrated here with * and it's these we wish to manipulate.

[python2-mock]*[python-zope-interface]*[pytz]*[pyOpenSSL.x86_64]

So now I have a way to quote $1 and replace $2 with , . The regex is simply \s and since this space may or may not be there as in the last position, we add ?.

[
    {
        "key": "ctrl+l",
        "command": "editor.action.insertSnippet",
        "args": {
          "snippet": "$1[${TM_SELECTED_TEXT/(\\w+-?\\.?\\w+-?\\w+)(\\s)?/\"$1\", /g}]"
        }
      }
]
# ctrl+l
["python2-mock", "python-zope-interface", "pytz", "pyOpenSSL.x86_64", ]

However, we still need to reference group2 somehow, replacing , with ${2:+,} is the answer.

[
    {
        "key": "ctrl+l",
        "command": "editor.action.insertSnippet",
        "args": {
          "snippet": "$1[${TM_SELECTED_TEXT/(\\w+-?\\.?\\w+-?\\w+)(\\s)?/\"$1\"${2:+, }/g}]"
        }
      }
]
# ctrl+l
["python2-mock", "python-zope-interface", "pytz", "pyOpenSSL.x86_64"]

The ${number:something} syntax is the "placeholder" syntax vaguely outlined in the documentation. Essentially we're replacing capture group2 specifically with , instead of just adding it onto the end of each element of capture group1.

To convert back from list to something you can paste into a bash terminal for yum you can use the rudementary delete operation. This needs to be refined but it works for this particular case.

[
    {
        "key": "ctrl+l",
        "command": "editor.action.insertSnippet",
        "args": {
          "snippet": "$1[${TM_SELECTED_TEXT/(\\w+-?\\.?\\w+-?\\w+)(\\s)?/\"$1\"${2:+, }/g}]"
        }
      },

      {
        "key": "ctrl+shift+l",
        "command": "editor.action.insertSnippet",
        "args": {
          "snippet": "$1${TM_SELECTED_TEXT/([^\\w-\\.])/ /g}"
        }
      }
]
# ctrl+shift+l
  python2-mock    python-zope-interface    pytz    pyOpenSSL.x86_64  

[^\\w-\\.] is saying all special characters but NOT ^ the - which is escaped here and the . since those are part of the package names I don't want to delete them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment